1
0
Files
electrum/tests/test_wizard.py
SomberNight acc52e392c tests: wizard: imported addrs: validate each addr with "is_address()"
This was already done *in the GUIs*, but the backend should definitely
do at least sanity-check-level validation like this.
2025-08-12 18:42:35 +00:00

830 lines
37 KiB
Python

import os
from electrum import SimpleConfig
from electrum.interface import ServerAddr
from electrum.network import NetworkParameters, ProxySettings
from electrum.plugin import Plugins, DeviceInfo, Device
from electrum.wizard import ServerConnectWizard, NewWalletWizard, WizardViewState
from electrum.daemon import Daemon
from electrum.wallet import Abstract_Wallet
from electrum import util
from electrum import slip39
from . import ElectrumTestCase
from .test_wallet_vertical import UNICODE_HORROR
class NetworkMock:
def __init__(self):
self.reset()
def reset(self):
self.run_called = False
self.parameters = NetworkParameters(server=None, proxy=None, auto_connect=None, oneserver=None)
def run_from_another_thread(self, *args, **kwargs):
self.run_called = True
def set_parameters(self, parameters):
self.parameters = parameters
def get_parameters(self):
return self.parameters
class DaemonMock:
def __init__(self, config: SimpleConfig):
self.config = config
self.network = NetworkMock()
class WizardTestCase(ElectrumTestCase):
def setUp(self):
super().setUp()
self.config = SimpleConfig({
'electrum_path': self.electrum_path,
'enable_plugin_trustedcoin': True,
})
self.wallet_path = os.path.join(self.electrum_path, "somewallet")
self.plugins = Plugins(self.config, gui_name='cmdline')
self.plugins.load_plugin_by_name('trustedcoin')
# note: hw plugins are loaded on-demand
def tearDown(self):
self.plugins.stop()
self.plugins.stopped_event.wait()
super().tearDown()
class ServerConnectWizardTestCase(WizardTestCase):
async def test_no_advanced(self):
w = ServerConnectWizard(DaemonMock(self.config))
v_init = w.start()
d = {'autoconnect': True, 'want_proxy': False}
self.assertTrue(w.is_last_view(v_init.view, d))
w.resolve_next(v_init.view, d)
self.assertEqual(True, self.config.NETWORK_AUTO_CONNECT)
async def test_server(self):
w = ServerConnectWizard(DaemonMock(self.config))
v_init = w.start()
d = {'autoconnect': False, 'want_proxy': False}
self.assertFalse(w.is_last_view(v_init.view, d))
v = w.resolve_next(v_init.view, d)
self.assertEqual('server_config', v.view)
self.assertEqual(False, self.config.NETWORK_AUTO_CONNECT)
async def test_proxy(self):
w = ServerConnectWizard(DaemonMock(self.config))
v_init = w.start()
w._daemon.network.reset()
d = {'autoconnect': True, 'want_proxy': True}
self.assertFalse(w.is_last_view(v_init.view, d))
v = w.resolve_next(v_init.view, d)
self.assertEqual('proxy_config', v.view)
self.assertEqual(True, self.config.NETWORK_AUTO_CONNECT)
d_proxy = {'enabled': True, 'mode': 'socks5', 'host': 'localhost', 'port': '1'}
d.update({'proxy': d_proxy})
v = w.resolve_next(v.view, d)
self.assertTrue(w.is_last_view(v.view, d))
self.assertTrue(w._daemon.network.run_called)
self.assertEqual(NetworkParameters(server=None, proxy=ProxySettings.from_dict(d_proxy), auto_connect=True, oneserver=None), w._daemon.network.parameters)
async def test_proxy_and_server(self):
w = ServerConnectWizard(DaemonMock(self.config))
v_init = w.start()
w._daemon.network.reset()
d = {'autoconnect': False, 'want_proxy': True}
self.assertFalse(w.is_last_view(v_init.view, d))
v = w.resolve_next(v_init.view, d)
self.assertEqual('proxy_config', v.view)
self.assertEqual(False, self.config.NETWORK_AUTO_CONNECT)
d_proxy = {'enabled': False}
d.update({'proxy': d_proxy})
v = w.resolve_next(v.view, d)
w._daemon.network.reset()
self.assertEqual('server_config', v.view)
d.update({'server': 'localhost:1:t'})
self.assertTrue(w.is_last_view(v.view, d))
v = w.resolve_next(v.view, d)
serverobj = ServerAddr.from_str_with_inference('localhost:1:t')
self.assertTrue(w._daemon.network.run_called)
self.assertEqual(NetworkParameters(server=serverobj, proxy=None, auto_connect=False, oneserver=False), w._daemon.network.parameters)
# TODO KeystoreWizard ("enable keystore")
class WalletWizardTestCase(WizardTestCase):
def _wizard_for(
self,
*,
name: str = "mywallet",
wallet_type: str,
) -> NewWalletWizard:
w = NewWalletWizard(DaemonMock(self.config), self.plugins)
if wallet_type == '2fa':
w.plugins.get_plugin('trustedcoin').extend_wizard(w)
v_init = w.start()
self.assertEqual('wallet_name', v_init.view)
d = {'wallet_name': name}
self.assertFalse(w.is_last_view(v_init.view, d))
v = w.resolve_next(v_init.view, d)
self.assertEqual('wallet_type', v.view)
d.update({'wallet_type': wallet_type})
w.resolve_next(v.view, d)
return w
def _set_password_and_check_address(
self,
*,
v: WizardViewState,
w: NewWalletWizard,
recv_addr: str | None, # "first addr" only makes sense for HD wallets
password: str | None = None,
encrypt_file: bool = False,
) -> Abstract_Wallet:
d = v.wizard_data
self.assertEqual('wallet_password', v.view)
d.update({'password': password, 'encrypt': encrypt_file})
self.assertTrue(w.is_last_view(v.view, d))
v = w.resolve_next(v.view, d)
wallet_path = os.path.join(w._daemon.config.get_datadir_wallet_path(), d['wallet_name'])
w.create_storage(wallet_path, d)
self.assertTrue(os.path.exists(wallet_path))
wallet = Daemon._load_wallet(wallet_path, password=password, config=self.config)
if recv_addr is not None:
self.assertEqual(recv_addr, wallet.get_receiving_addresses()[0])
self.assertEqual(bool(password), wallet.has_password())
self.assertEqual(encrypt_file, wallet.has_storage_encryption())
return wallet
async def test_set_password_and_encrypt_file(self):
w = self._wizard_for(wallet_type='standard')
v = w._current
d = v.wizard_data
self.assertEqual('keystore_type', v.view)
d.update({'keystore_type': 'haveseed'})
v = w.resolve_next(v.view, d)
self.assertEqual('have_seed', v.view)
d.update({'seed': '9dk', 'seed_type': 'segwit', 'seed_extend': False, 'seed_variant': 'electrum'})
v = w.resolve_next(v.view, d)
wallet = self._set_password_and_check_address(
v=v, w=w, recv_addr="bc1qq2tmmcngng78nllq2pvrkchcdukemtj56uyue0",
password="1234", encrypt_file=True,
)
self.assertTrue(wallet.has_password())
with self.assertRaises(util.InvalidPassword):
wallet.check_password("0000")
wallet.check_password("1234")
self.assertTrue(wallet.has_keystore_encryption())
self.assertTrue(wallet.has_storage_encryption())
async def test_set_password_but_dont_encrypt_file(self):
w = self._wizard_for(wallet_type='standard')
v = w._current
d = v.wizard_data
self.assertEqual('keystore_type', v.view)
d.update({'keystore_type': 'haveseed'})
v = w.resolve_next(v.view, d)
self.assertEqual('have_seed', v.view)
d.update({'seed': '9dk', 'seed_type': 'segwit', 'seed_extend': False, 'seed_variant': 'electrum'})
v = w.resolve_next(v.view, d)
wallet = self._set_password_and_check_address(
v=v, w=w, recv_addr="bc1qq2tmmcngng78nllq2pvrkchcdukemtj56uyue0",
password="1234", encrypt_file=False,
)
self.assertTrue(wallet.has_password())
with self.assertRaises(util.InvalidPassword):
wallet.check_password("0000")
wallet.check_password("1234")
self.assertTrue(wallet.has_keystore_encryption())
self.assertFalse(wallet.has_storage_encryption())
async def test_create_standard_wallet_createseed(self):
w = self._wizard_for(wallet_type='standard')
v = w._current
d = v.wizard_data
self.assertEqual('keystore_type', v.view)
d.update({'keystore_type': 'createseed'})
v = w.resolve_next(v.view, d)
self.assertEqual('create_seed', v.view)
d.update({
'seed': '9dk', 'seed_type': 'segwit', 'seed_extend': False, 'seed_variant': 'electrum',
})
v = w.resolve_next(v.view, d)
self.assertEqual('confirm_seed', v.view)
v = w.resolve_next(v.view, d)
self._set_password_and_check_address(v=v, w=w, recv_addr="bc1qq2tmmcngng78nllq2pvrkchcdukemtj56uyue0")
async def test_create_standard_wallet_createseed_passphrase(self):
w = self._wizard_for(wallet_type='standard')
v = w._current
d = v.wizard_data
self.assertEqual('keystore_type', v.view)
d.update({'keystore_type': 'createseed'})
v = w.resolve_next(v.view, d)
self.assertEqual('create_seed', v.view)
d.update({
'seed': '9dk', 'seed_type': 'segwit', 'seed_extend': True, 'seed_variant': 'electrum',
})
v = w.resolve_next(v.view, d)
self.assertEqual('create_ext', v.view)
d.update({'seed_extra_words': UNICODE_HORROR})
v = w.resolve_next(v.view, d)
self.assertEqual('confirm_seed', v.view)
v = w.resolve_next(v.view, d)
self.assertEqual('confirm_ext', v.view)
v = w.resolve_next(v.view, d)
self._set_password_and_check_address(v=v, w=w, recv_addr="bc1qgvx24uzdv4mapfmtlu8azty5fxdcw9ghxu4pr4")
async def test_create_standard_wallet_haveseed_electrum_oldseed(self):
w = self._wizard_for(wallet_type='standard')
v = w._current
d = v.wizard_data
self.assertEqual('keystore_type', v.view)
d.update({'keystore_type': 'haveseed'})
v = w.resolve_next(v.view, d)
self.assertEqual('have_seed', v.view)
d.update({
'seed': 'powerful random nobody notice nothing important anyway look away hidden message over',
'seed_type': 'old', 'seed_extend': False, 'seed_variant': 'electrum'})
v = w.resolve_next(v.view, d)
self._set_password_and_check_address(v=v, w=w, recv_addr="1FJEEB8ihPMbzs2SkLmr37dHyRFzakqUmo")
async def test_create_standard_wallet_haveseed_electrum_oldseed_passphrase(self):
w = self._wizard_for(wallet_type='standard')
v = w._current
d = v.wizard_data
self.assertEqual('keystore_type', v.view)
d.update({'keystore_type': 'haveseed'})
v = w.resolve_next(v.view, d)
self.assertEqual('have_seed', v.view)
d.update({
'seed': 'powerful random nobody notice nothing important anyway look away hidden message over',
'seed_type': 'old', 'seed_extend': True, 'seed_variant': 'electrum'})
v = w.resolve_next(v.view, d)
# FIXME this diverges from the actual GUIs :(
# the GUIs do validation using wizard.validate_seed() and don't go to 'have_ext' for next view.
# the validation should be moved to the base impl!
self.assertEqual('have_ext', v.view)
d.update({'seed_extra_words': UNICODE_HORROR})
with self.assertRaises(Exception) as ctx:
v = w.resolve_next(v.view, d)
self.assertTrue("cannot have passphrase" in ctx.exception.args[0])
async def test_create_standard_wallet_haveseed_electrum(self):
w = self._wizard_for(wallet_type='standard')
v = w._current
d = v.wizard_data
self.assertEqual('keystore_type', v.view)
d.update({'keystore_type': 'haveseed'})
v = w.resolve_next(v.view, d)
self.assertEqual('have_seed', v.view)
d.update({'seed': '9dk', 'seed_type': 'segwit', 'seed_extend': False, 'seed_variant': 'electrum'})
v = w.resolve_next(v.view, d)
self._set_password_and_check_address(v=v, w=w, recv_addr="bc1qq2tmmcngng78nllq2pvrkchcdukemtj56uyue0")
async def test_create_standard_wallet_haveseed_electrum_passphrase(self):
w = self._wizard_for(wallet_type='standard')
v = w._current
d = v.wizard_data
self.assertEqual('keystore_type', v.view)
d.update({'keystore_type': 'haveseed'})
v = w.resolve_next(v.view, d)
self.assertEqual('have_seed', v.view)
d.update({'seed': '9dk', 'seed_type': 'segwit', 'seed_extend': True, 'seed_variant': 'electrum'})
v = w.resolve_next(v.view, d)
self.assertEqual('have_ext', v.view)
d.update({'seed_extra_words': UNICODE_HORROR})
v = w.resolve_next(v.view, d)
self._set_password_and_check_address(v=v, w=w, recv_addr="bc1qgvx24uzdv4mapfmtlu8azty5fxdcw9ghxu4pr4")
async def test_create_standard_wallet_have_master_key(self):
w = self._wizard_for(wallet_type='standard')
v = w._current
d = v.wizard_data
self.assertEqual('keystore_type', v.view)
d.update({'keystore_type': 'masterkey'})
v = w.resolve_next(v.view, d)
self.assertEqual('have_master_key', v.view)
d.update({
'master_key': 'zpub6nAZodjgiMNf9zzX1pTqd6ZVX61ax8azhUDnWRumKVUr1VYATVoqAuqv3qKsb8WJXjxei4wei2p4vnMG9RnpKnen2kmgdhvZUmug2NnHNsr',
'multisig_master_pubkey': 'zpub6nAZodjgiMNf9zzX1pTqd6ZVX61ax8azhUDnWRumKVUr1VYATVoqAuqv3qKsb8WJXjxei4wei2p4vnMG9RnpKnen2kmgdhvZUmug2NnHNsr'})
v = w.resolve_next(v.view, d)
self._set_password_and_check_address(v=v, w=w, recv_addr="bc1qq2tmmcngng78nllq2pvrkchcdukemtj56uyue0")
async def test_create_standard_wallet_haveseed_bip39(self):
w = self._wizard_for(wallet_type='standard')
v = w._current
d = v.wizard_data
self.assertEqual('keystore_type', v.view)
d.update({'keystore_type': 'haveseed'})
v = w.resolve_next(v.view, d)
self.assertEqual('have_seed', v.view)
d.update({'seed': '9dk', 'seed_type': 'bip39', 'seed_extend': False, 'seed_variant': 'bip39'})
v = w.resolve_next(v.view, d)
self.assertEqual('script_and_derivation', v.view)
d.update({'script_type': 'p2wpkh', 'derivation_path': 'm'})
v = w.resolve_next(v.view, d)
self._set_password_and_check_address(v=v, w=w, recv_addr="bc1qrjr8qn4669jgr3s34f2pyj9awhz02eyvk5eh8g")
async def test_create_standard_wallet_haveseed_bip39_passphrase(self):
w = self._wizard_for(wallet_type='standard')
v = w._current
d = v.wizard_data
self.assertEqual('keystore_type', v.view)
d.update({'keystore_type': 'haveseed'})
v = w.resolve_next(v.view, d)
self.assertEqual('have_seed', v.view)
d.update({'seed': '9dk', 'seed_type': 'bip39', 'seed_extend': True, 'seed_variant': 'bip39'})
v = w.resolve_next(v.view, d)
self.assertEqual('have_ext', v.view)
d.update({'seed_extra_words': UNICODE_HORROR})
v = w.resolve_next(v.view, d)
self.assertEqual('script_and_derivation', v.view)
d.update({'script_type': 'p2wpkh', 'derivation_path': 'm'})
v = w.resolve_next(v.view, d)
self._set_password_and_check_address(v=v, w=w, recv_addr="bc1qjexrunguxz8rlfuul8h4apafyh3sq5yp9kg98j")
async def test_create_standard_wallet_haveseed_slip39(self):
w = self._wizard_for(wallet_type='standard')
v = w._current
d = v.wizard_data
self.assertEqual('keystore_type', v.view)
d.update({'keystore_type': 'haveseed'})
v = w.resolve_next(v.view, d)
self.assertEqual('have_seed', v.view)
# SLIP39 shares (128 bits, 2 groups from 1 of 1, 1 of 1, 3 of 5, 2 of 6)
mnemonics = [
"fact else acrobat romp analysis usher havoc vitamins analysis garden prevent romantic silent dramatic adjust priority mailman plains vintage else",
"fact else ceramic round craft lips snake faint adorn square bucket deadline violence guitar greatest academic stadium snake frequent memory",
"fact else ceramic scatter counter remove club forbid busy cause taxi forecast prayer uncover living type training forward software pumps",
"fact else ceramic shaft clock crowd detect cleanup wildlife depict include trip profile isolate express category wealthy advance garden mixture",
]
encrypted_seed = slip39.recover_ems(mnemonics)
d.update({'seed': encrypted_seed, 'seed_variant': 'slip39', 'seed_type': 'slip39', 'seed_extend': False})
v = w.resolve_next(v.view, d)
self.assertEqual('script_and_derivation', v.view)
d.update({
'script_type': 'p2wpkh', 'derivation_path': 'm/84h/0h/0h',
'multisig_master_pubkey': 'zpub6riQosasrLdM1rmmohyUHtseLYeCBKP55Xe1LTT7jyKFM6dMMZPYVx5ug6zH2gZ6XFGcUYubjbm43vXHecTzNmoMS3yfp6oeZT3GetsGFt4'})
v = w.resolve_next(v.view, d)
self._set_password_and_check_address(v=v, w=w, recv_addr="bc1q40ksvkl7wvc2l999ppl48swgt3rsl45ykyyrjn")
async def test_create_standard_wallet_haveseed_slip39_passphrase(self):
w = self._wizard_for(wallet_type='standard')
v = w._current
d = v.wizard_data
self.assertEqual('keystore_type', v.view)
d.update({'keystore_type': 'haveseed'})
v = w.resolve_next(v.view, d)
self.assertEqual('have_seed', v.view)
# SLIP39 shares (128 bits, 2 groups from 1 of 1, 1 of 1, 3 of 5, 2 of 6)
mnemonics = [
"fact else acrobat romp analysis usher havoc vitamins analysis garden prevent romantic silent dramatic adjust priority mailman plains vintage else",
"fact else ceramic round craft lips snake faint adorn square bucket deadline violence guitar greatest academic stadium snake frequent memory",
"fact else ceramic scatter counter remove club forbid busy cause taxi forecast prayer uncover living type training forward software pumps",
"fact else ceramic shaft clock crowd detect cleanup wildlife depict include trip profile isolate express category wealthy advance garden mixture",
]
encrypted_seed = slip39.recover_ems(mnemonics)
d.update({'seed': encrypted_seed, 'seed_variant': 'slip39', 'seed_type': 'slip39', 'seed_extend': True})
v = w.resolve_next(v.view, d)
self.assertEqual('have_ext', v.view)
d.update({'seed_extra_words': 'TREZOR'})
v = w.resolve_next(v.view, d)
self.assertEqual('script_and_derivation', v.view)
d.update({
'script_type': 'p2wpkh', 'derivation_path': 'm/84h/0h/0h',
'multisig_master_pubkey': 'zpub6s6A9ynh7TT1sPXmQyu8S6g7kxMF6iSZkM3NmgF4w7CtpsGgg56aouYSWHgAoMy186a8FRT8zkmhcwV5SWKFFQfMpvV8C9Ft4woWSzD5sXz'})
v = w.resolve_next(v.view, d)
self._set_password_and_check_address(v=v, w=w, recv_addr="bc1qs2svwhfz47qv9qju2waa6prxzv5f522fc4p06t")
async def test_2fa_createseed(self):
self.assertTrue(self.config.get('enable_plugin_trustedcoin'))
w = self._wizard_for(wallet_type='2fa')
v = w._current
d = v.wizard_data
self.assertEqual('trustedcoin_start', v.view)
v = w.resolve_next(v.view, d)
self.assertEqual('trustedcoin_choose_seed', v.view)
d.update({'keystore_type': 'createseed'})
v = w.resolve_next(v.view, d)
self.assertEqual('trustedcoin_create_seed', v.view)
d.update({
'seed': 'oblige basket safe educate whale bacon celery demand novel slice various awkward',
'seed_type': '2fa', 'seed_extend': False, 'seed_variant': 'electrum',
})
v = w.resolve_next(v.view, d)
self.assertEqual('trustedcoin_confirm_seed', v.view)
v = w.resolve_next(v.view, d)
self.assertEqual('trustedcoin_tos', v.view)
v = w.resolve_next(v.view, d)
self.assertEqual('trustedcoin_show_confirm_otp', v.view)
v = w.resolve_next(v.view, d)
self._set_password_and_check_address(v=v, w=w, recv_addr="bc1qnf5qafvpx0afk47433j3tt30pqkxp5wa263m77wt0pvyqq67rmfs522m94")
async def test_2fa_haveseed_keep2FAenabled(self):
self.assertTrue(self.config.get('enable_plugin_trustedcoin'))
w = self._wizard_for(wallet_type='2fa')
v = w._current
d = v.wizard_data
self.assertEqual('trustedcoin_start', v.view)
v = w.resolve_next(v.view, d)
self.assertEqual('trustedcoin_choose_seed', v.view)
d.update({'keystore_type': 'haveseed'})
v = w.resolve_next(v.view, d)
self.assertEqual('trustedcoin_have_seed', v.view)
d.update({
'seed': 'oblige basket safe educate whale bacon celery demand novel slice various awkward',
'seed_type': '2fa', 'seed_extend': False, 'seed_variant': 'electrum',
})
v = w.resolve_next(v.view, d)
self.assertEqual('trustedcoin_keep_disable', v.view)
d.update({'trustedcoin_keepordisable': 'keep'})
v = w.resolve_next(v.view, d)
self.assertEqual('trustedcoin_tos', v.view)
v = w.resolve_next(v.view, d)
self.assertEqual('trustedcoin_show_confirm_otp', v.view)
v = w.resolve_next(v.view, d)
self._set_password_and_check_address(v=v, w=w, recv_addr="bc1qnf5qafvpx0afk47433j3tt30pqkxp5wa263m77wt0pvyqq67rmfs522m94")
async def test_2fa_haveseed_disable2FA(self):
self.assertTrue(self.config.get('enable_plugin_trustedcoin'))
w = self._wizard_for(wallet_type='2fa')
v = w._current
d = v.wizard_data
self.assertEqual('trustedcoin_start', v.view)
v = w.resolve_next(v.view, d)
self.assertEqual('trustedcoin_choose_seed', v.view)
d.update({'keystore_type': 'haveseed'})
v = w.resolve_next(v.view, d)
self.assertEqual('trustedcoin_have_seed', v.view)
d.update({
'seed': 'oblige basket safe educate whale bacon celery demand novel slice various awkward',
'seed_type': '2fa', 'seed_extend': False, 'seed_variant': 'electrum',
})
v = w.resolve_next(v.view, d)
self.assertEqual('trustedcoin_keep_disable', v.view)
d.update({'trustedcoin_keepordisable': 'disable'})
v = w.resolve_next(v.view, d)
self._set_password_and_check_address(v=v, w=w, recv_addr="bc1qnf5qafvpx0afk47433j3tt30pqkxp5wa263m77wt0pvyqq67rmfs522m94")
async def test_2fa_haveseed_passphrase(self):
self.assertTrue(self.config.get('enable_plugin_trustedcoin'))
w = self._wizard_for(wallet_type='2fa')
v = w._current
d = v.wizard_data
self.assertEqual('trustedcoin_start', v.view)
v = w.resolve_next(v.view, d)
self.assertEqual('trustedcoin_choose_seed', v.view)
d.update({'keystore_type': 'haveseed'})
v = w.resolve_next(v.view, d)
self.assertEqual('trustedcoin_have_seed', v.view)
d.update({
'seed': 'oblige basket safe educate whale bacon celery demand novel slice various awkward',
'seed_type': '2fa', 'seed_extend': True, 'seed_variant': 'electrum',
})
v = w.resolve_next(v.view, d)
self.assertEqual('trustedcoin_have_ext', v.view)
d.update({'seed_extra_words': UNICODE_HORROR})
v = w.resolve_next(v.view, d)
self.assertEqual('trustedcoin_keep_disable', v.view)
d.update({'trustedcoin_keepordisable': 'keep'})
v = w.resolve_next(v.view, d)
self.assertEqual('trustedcoin_tos', v.view)
v = w.resolve_next(v.view, d)
self.assertEqual('trustedcoin_show_confirm_otp', v.view)
v = w.resolve_next(v.view, d)
self._set_password_and_check_address(v=v, w=w, recv_addr="bc1qcnu9ay4v3w0tawuxe6wlh6mh33rrpauqnufdgkxx7we8vpx3e6wqa25qud")
async def test_create_standard_wallet_trezor(self):
# bip39 seed for trezor: "history six okay anchor sheriff flock atom tomorrow foster aerobic eternal foam"
w = self._wizard_for(wallet_type='standard')
v = w._current
d = v.wizard_data
self.assertEqual('keystore_type', v.view)
d.update({'keystore_type': 'hardware'})
v = w.resolve_next(v.view, d)
self.assertEqual('choose_hardware_device', v.view)
d.update({
'hardware_device': (
'trezor',
DeviceInfo(
device=Device(path='webusb:002:1', interface_number=-1, id_='webusb:002:1', product_key='Trezor', usage_page=0, transport_ui_string='webusb:002:1'),
label='trezor_unittests', initialized=True, exception=None, plugin_name='trezor', soft_device_id='088C3F260B66F60E15DE0FA5', model_name='Trezor T'))})
v = w.resolve_next(v.view, d)
self.assertEqual('trezor_start', v.view)
d.update({'script_type': 'p2wpkh', 'derivation_path': 'm/84h/0h/0h'})
v = w.resolve_next(v.view, d)
self.assertEqual('trezor_xpub', v.view)
d.update({
'hw_type': 'trezor', 'master_key': 'zpub6qqp9XwsVMsovwzayXhFDJTpoc8VFoNy6mjkJHygou9NPRPDNR7MXVp9DM7qpacWwoePFWg7Gt5L5xnKNLmZYH8AFoTm2AAZA7LasycHu3n',
'root_fingerprint': '6306ee35', 'label': 'trezor_unittests', 'soft_device_id': '088C3F260B66F60E15DE0FA5',
'multisig_master_pubkey': 'zpub6qqp9XwsVMsovwzayXhFDJTpoc8VFoNy6mjkJHygou9NPRPDNR7MXVp9DM7qpacWwoePFWg7Gt5L5xnKNLmZYH8AFoTm2AAZA7LasycHu3n'})
v = w.resolve_next(v.view, d)
self.assertEqual('wallet_password_hardware', v.view)
d.update({'password': '03a580deb85ef85654ed177fc049867ce915a8b392a34a524123870925e48a5b9e', 'encrypt': True, 'xpub_encrypt': True})
self.assertTrue(w.is_last_view(v.view, d))
v = w.resolve_next(v.view, d)
wallet_path = os.path.join(w._daemon.config.get_datadir_wallet_path(), d['wallet_name'])
w.create_storage(wallet_path, d)
self.assertTrue(os.path.exists(wallet_path))
wallet = Daemon._load_wallet(wallet_path, password=d['password'], config=self.config)
self.assertEqual("bc1q7ltf4aq95rj695fu5aaa5mx5m9p55xyr2fy6y0", wallet.get_receiving_addresses()[0])
self.assertTrue(wallet.has_password())
self.assertTrue(wallet.has_storage_encryption())
async def test_unlock_hw_trezor(self):
# bip39 seed for trezor: "history six okay anchor sheriff flock atom tomorrow foster aerobic eternal foam"
w = NewWalletWizard(DaemonMock(self.config), self.plugins)
v = w.start()
self.assertEqual('wallet_name', v.view)
d = {
'wallet_name': 'mywallet',
'wallet_exists': True, 'wallet_is_open': False, 'wallet_needs_hw_unlock': True,}
self.assertFalse(w.is_last_view(v.view, d))
v = w.resolve_next(v.view, d)
self.assertEqual('hw_unlock', v.view)
d.update({
'hardware_device': (
'trezor',
DeviceInfo(
device=Device(path='webusb:002:1', interface_number=-1, id_='webusb:002:1', product_key='Trezor', usage_page=0, transport_ui_string='webusb:002:1'),
label='trezor_unittests', initialized=True, exception=None, plugin_name='trezor', soft_device_id='088C3F260B66F60E15DE0FA5', model_name='Trezor T'))})
v = w.resolve_next(v.view, d)
self.assertEqual('trezor_unlock', v.view)
d.update({'password': '03a580deb85ef85654ed177fc049867ce915a8b392a34a524123870925e48a5b9e'})
self.assertTrue(w.is_last_view(v.view, d))
v = w.resolve_next(v.view, d)
async def test_create_multisig_wallet_2of2_createseed_cosigner2hasmasterkey(self):
w = self._wizard_for(wallet_type='multisig')
v = w._current
d = v.wizard_data
self.assertEqual('multisig', v.view)
d.update({'multisig_participants': 2, 'multisig_signatures': 2, 'multisig_cosigner_data': {}})
v = w.resolve_next(v.view, d)
self.assertEqual('keystore_type', v.view)
d.update({'keystore_type': 'createseed'})
v = w.resolve_next(v.view, d)
self.assertEqual('create_seed', v.view)
d.update({
'seed': 'eager divert pigeon dentist punch festival manage smart globe regular adult cash',
'seed_type': 'segwit', 'seed_extend': False, 'seed_variant': 'electrum'})
v = w.resolve_next(v.view, d)
self.assertEqual('confirm_seed', v.view)
d.update({'multisig_master_pubkey': 'Zpub6y7YR1dmZZV4f5rRm6dJCKSqqxZhKUxc8PkssXm84k2bzbGYkL22ugC4aZxVxC1qz4yo53Zwz1c1kiSHmybB4JjCsjCPjzygSsN1UcdCcvB'})
v = w.resolve_next(v.view, d)
self.assertEqual('multisig_cosigner_keystore', v.view)
# 2nd cosigner uses Zpub from "9dk" seed
d['multisig_cosigner_data']['2'] = {'keystore_type': 'masterkey'}
d.update({
'multisig_current_cosigner': 2, 'cosigner_keystore_type': 'masterkey'})
v = w.resolve_next(v.view, d)
self.assertEqual('multisig_cosigner_key', v.view)
d['multisig_cosigner_data']['2'].update({'master_key': 'Zpub6y4evsU8HJw2d7ZH8QNyC6UKWHyxinAuQKkD6btsEZMbamy96UnefnM4sZp2K38rdiUssEhNq9TBpJ8Bh1GZCGTFpnYz8jM9pAdS6vk5VQs'})
v = w.resolve_next(v.view, d)
self._set_password_and_check_address(v=v, w=w, recv_addr="bc1qg39tkymxwq4tn2ly6c3lmnyvsy94jyw52rdvfqkzdv2slvlj9xcsfy63vc")
async def test_create_multisig_wallet_3of6_haveseed_passphrase__cs2hasbip39__cs3zpub__cs4trezor__cs5seedandpassphrase__cs6zprv(self):
w = self._wizard_for(wallet_type='multisig')
v = w._current
d = v.wizard_data
self.assertEqual('multisig', v.view)
d.update({'multisig_participants': 6, 'multisig_signatures': 3, 'multisig_cosigner_data': {}})
v = w.resolve_next(v.view, d)
self.assertEqual('keystore_type', v.view)
d.update({'keystore_type': 'haveseed'})
v = w.resolve_next(v.view, d)
self.assertEqual('have_seed', v.view)
d.update({
'seed': '9dk',
'seed_variant': 'electrum', 'seed_type': 'segwit', 'seed_extend': True})
v = w.resolve_next(v.view, d)
self.assertEqual('have_ext', v.view)
d.update({
'seed_extra_words': UNICODE_HORROR,
'multisig_master_pubkey': 'Zpub6zAYrXzLbLwWFCkahiB3fQz4KMUm68RsoGVHkM5aBjzHBGnQ9orvy7PKuFvMj4gyJXhFW5uFzHBgDDYFEPS75b3ADq3yvtuEJF86ZgLLyeL'})
v = w.resolve_next(v.view, d)
self.assertEqual('multisig_cosigner_keystore', v.view)
# 2nd cosigner
d['multisig_cosigner_data']['2'] = {'keystore_type': 'haveseed'}
d.update({
'multisig_current_cosigner': 2, 'cosigner_keystore_type': 'haveseed'})
v = w.resolve_next(v.view, d)
self.assertEqual('multisig_cosigner_seed', v.view)
d['multisig_cosigner_data']['2'].update({
'seed': 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
'seed_variant': 'bip39', 'seed_type': 'bip39', 'seed_extend': False})
v = w.resolve_next(v.view, d)
self.assertEqual('multisig_cosigner_script_and_derivation', v.view)
d['multisig_cosigner_data']['2'].update({
'script_type': 'p2wsh', 'derivation_path': 'm/48h/0h/0h/2h'})
v = w.resolve_next(v.view, d)
self.assertEqual('multisig_cosigner_keystore', v.view)
# 3rd cosigner uses Zpub from "9dk" seed
d['multisig_cosigner_data']['3'] = {'keystore_type': 'masterkey'}
d.update({
'multisig_current_cosigner': 3, 'cosigner_keystore_type': 'masterkey'})
v = w.resolve_next(v.view, d)
self.assertEqual('multisig_cosigner_key', v.view)
d['multisig_cosigner_data']['3'].update({
'master_key': 'Zpub6y4evsU8HJw2d7ZH8QNyC6UKWHyxinAuQKkD6btsEZMbamy96UnefnM4sZp2K38rdiUssEhNq9TBpJ8Bh1GZCGTFpnYz8jM9pAdS6vk5VQs'})
v = w.resolve_next(v.view, d)
self.assertEqual('multisig_cosigner_keystore', v.view)
# 4th cosigner
d['multisig_cosigner_data']['4'] = {'keystore_type': 'hardware'}
d.update({
'multisig_current_cosigner': 4, 'cosigner_keystore_type': 'hardware'})
v = w.resolve_next(v.view, d)
self.assertEqual('multisig_cosigner_hardware', v.view)
d['multisig_cosigner_data']['4'].update({
'hardware_device': (
'trezor',
DeviceInfo(
device=Device(path='webusb:002:1', interface_number=-1, id_='webusb:002:1', product_key='Trezor', usage_page=0, transport_ui_string='webusb:002:1'),
label='trezor_unittests', initialized=True, exception=None, plugin_name='trezor', soft_device_id='088C3F260B66F60E15DE0FA5', model_name='Trezor T'))})
v = w.resolve_next(v.view, d)
self.assertEqual('trezor_start', v.view)
d['multisig_cosigner_data']['4'].update({
'script_type': 'p2wsh', 'derivation_path': 'm/48h/0h/0h/2h'})
v = w.resolve_next(v.view, d)
self.assertEqual('trezor_xpub', v.view)
d['multisig_cosigner_data']['4'].update({
'hw_type': 'trezor', 'master_key': 'Zpub75t8XsK4GVa2EyQtjvT9auayKwonGaQJ149qB9r11o5iikugxJ99hYgbcaTdCGjd4DUdz4z2bqAtmDv2s8UihG1AnbzBufSG82GxjMDfVUn',
'root_fingerprint': '6306ee35', 'label': 'trezor_unittests', 'soft_device_id': '088C3F260B66F60E15DE0FA5'})
v = w.resolve_next(v.view, d)
self.assertEqual('multisig_cosigner_keystore', v.view)
# 5th cosigner
d['multisig_cosigner_data']['5'] = {'keystore_type': 'haveseed'}
d.update({
'multisig_current_cosigner': 5, 'cosigner_keystore_type': 'haveseed'})
v = w.resolve_next(v.view, d)
self.assertEqual('multisig_cosigner_seed', v.view)
d['multisig_cosigner_data']['5'].update({
'seed': 'abandon bike',
'seed_variant': 'electrum', 'seed_type': 'segwit', 'seed_extend': True})
v = w.resolve_next(v.view, d)
self.assertEqual('multisig_cosigner_have_ext', v.view)
d['multisig_cosigner_data']['5'].update({
'seed_extra_words': UNICODE_HORROR})
v = w.resolve_next(v.view, d)
self.assertEqual('multisig_cosigner_keystore', v.view)
# 6th cosigner uses Zprv from "abandon bike" seed
d['multisig_cosigner_data']['6'] = {'keystore_type': 'masterkey'}
d.update({
'multisig_current_cosigner': 6, 'cosigner_keystore_type': 'masterkey'})
v = w.resolve_next(v.view, d)
self.assertEqual('multisig_cosigner_key', v.view)
d['multisig_cosigner_data']['6'].update({
'master_key': 'ZprvAjWENdvYc1Ctvppxm4Z67U4EoiDy5VXKNvWmVAZshy7UjgKggu1UcAH7MqRqTaHVunuEPZ7o51wCrsZnJXPJtzHnAoxNmMLWFMHC7uvUN5P'})
v = w.resolve_next(v.view, d)
self._set_password_and_check_address(v=v, w=w, recv_addr="bc1qtuzp7rectyjquax5c3p80eletswhp6cxslye749l47h4m9x92hzs6cmymy")
async def test_create_imported_wallet_from_addresses(self):
w = self._wizard_for(wallet_type='imported')
v = w._current
d = v.wizard_data
self.assertEqual('imported', v.view)
d.update({
'address_list':
'14gcRovpkCoGkCNBivQBvw7eso7eiNAbxG\n'
'35ZqQJcBQMZ1rsv8aSuJ2wkC7ohUCQMJbT\n'
'BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4\n'
'bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y\n'})
v = w.resolve_next(v.view, d)
wallet = self._set_password_and_check_address(v=v, w=w, recv_addr=None)
self.assertEqual(
set(wallet.get_receiving_addresses()),
{
"14gcRovpkCoGkCNBivQBvw7eso7eiNAbxG",
"35ZqQJcBQMZ1rsv8aSuJ2wkC7ohUCQMJbT",
"BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", # TODO normalize to lowercase?
"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y",
},
)
async def test_create_imported_wallet_from_addresses__invalid_input(self):
w = self._wizard_for(wallet_type='imported')
v = w._current
d = v.wizard_data
self.assertEqual('imported', v.view)
d.update({
'address_list':
'garbagegarbage\n'
'35ZqQJcBQMZ1rsv8aSuJ2wkC7ohUCQMJbT\n'
})
v = w.resolve_next(v.view, d)
with self.assertRaises(AssertionError) as ctx:
wallet = self._set_password_and_check_address(v=v, w=w, recv_addr=None)
self.assertTrue("expected bitcoin addr" in ctx.exception.args[0])
async def test_create_imported_wallet_from_wif_keys(self):
w = self._wizard_for(wallet_type='imported')
v = w._current
d = v.wizard_data
self.assertEqual('imported', v.view)
d.update({
'private_key_list':
'p2wpkh:L1cgMEnShp73r9iCukoPE3MogLeueNYRD9JVsfT1zVHyPBR3KqBY\n'
'p2pkh:KyQ2voUQj71P6E9KyDFqQoYMMm3yKKAPMKbfqZccib6xWxbWHCex\n'
'p2pkh:5JuecQZ1nH4VCQRQJTQjB4yu93BU6NmnAkDoGRdHX2PyH2E8QVX\n'})
v = w.resolve_next(v.view, d)
wallet = self._set_password_and_check_address(v=v, w=w, recv_addr=None)
self.assertEqual(
set(wallet.get_receiving_addresses()),
{"bc1qq2tmmcngng78nllq2pvrkchcdukemtj56uyue0", "1LNvv5h6QHoYv1nJcqrp13T2TBkD2sUGn1", "1FJEEB8ihPMbzs2SkLmr37dHyRFzakqUmo"},
)