@@ -440,10 +440,6 @@ class WCExtendKeystore(WalletWizardComponent):
|
|||||||
|
|
||||||
def apply(self):
|
def apply(self):
|
||||||
self.wizard_data['keystore_type'] = self.choice_w.selected_key
|
self.wizard_data['keystore_type'] = self.choice_w.selected_key
|
||||||
if multisig_type(self.wizard_data['wallet_type']):
|
|
||||||
self.wizard_data['multisig_participants'] = 2
|
|
||||||
self.wizard_data['multisig_signatures'] = 2
|
|
||||||
self.wizard_data['multisig_cosigner_data'] = {}
|
|
||||||
|
|
||||||
|
|
||||||
class WCCreateSeed(WalletWizardComponent):
|
class WCCreateSeed(WalletWizardComponent):
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from electrum.wallet_db import WalletDB
|
|||||||
from electrum.bip32 import normalize_bip32_derivation, xpub_type
|
from electrum.bip32 import normalize_bip32_derivation, xpub_type
|
||||||
from electrum import keystore, mnemonic, bitcoin
|
from electrum import keystore, mnemonic, bitcoin
|
||||||
from electrum.mnemonic import is_any_2fa_seed_type, can_seed_have_passphrase
|
from electrum.mnemonic import is_any_2fa_seed_type, can_seed_have_passphrase
|
||||||
|
from electrum.util import multisig_type
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from electrum.daemon import Daemon
|
from electrum.daemon import Daemon
|
||||||
@@ -257,6 +258,14 @@ class KeystoreWizard(AbstractWizard):
|
|||||||
# one at a time
|
# one at a time
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _convert_wallet_type(self, wizard_data: dict) -> None:
|
||||||
|
assert 'wallet_type' in wizard_data
|
||||||
|
if multisig_type(wizard_data['wallet_type']):
|
||||||
|
wizard_data['wallet_type'] = 'multisig' # convert from e.g. "2of2" to "multisig"
|
||||||
|
wizard_data['multisig_participants'] = 2
|
||||||
|
wizard_data['multisig_signatures'] = 2
|
||||||
|
wizard_data['multisig_cosigner_data'] = {}
|
||||||
|
|
||||||
def start(self, *, start_viewstate: WizardViewState = None) -> WizardViewState:
|
def start(self, *, start_viewstate: WizardViewState = None) -> WizardViewState:
|
||||||
self.reset()
|
self.reset()
|
||||||
if start_viewstate is None:
|
if start_viewstate is None:
|
||||||
@@ -265,6 +274,7 @@ class KeystoreWizard(AbstractWizard):
|
|||||||
self._current = WizardViewState(start_view, {}, params)
|
self._current = WizardViewState(start_view, {}, params)
|
||||||
else:
|
else:
|
||||||
self._current = start_viewstate
|
self._current = start_viewstate
|
||||||
|
self._convert_wallet_type(self._current.wizard_data) # mutating in-place
|
||||||
return self._current
|
return self._current
|
||||||
|
|
||||||
# returns (sub)dict of current cosigner (or root if first)
|
# returns (sub)dict of current cosigner (or root if first)
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ from typing import Sequence
|
|||||||
import asyncio
|
import asyncio
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from electrum import storage, bitcoin, keystore, bip32, slip39, wallet
|
from electrum import bitcoin, keystore, bip32, slip39, wallet
|
||||||
|
from electrum.wallet_db import WalletDB
|
||||||
|
from electrum.storage import WalletStorage
|
||||||
from electrum import SimpleConfig
|
from electrum import SimpleConfig
|
||||||
from electrum import util
|
from electrum import util
|
||||||
from electrum.address_synchronizer import TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_LOCAL, TX_HEIGHT_FUTURE
|
from electrum.address_synchronizer import TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_LOCAL, TX_HEIGHT_FUTURE
|
||||||
@@ -35,7 +37,6 @@ class WalletIntegrityHelper:
|
|||||||
|
|
||||||
gap_limit = 1 # make tests run faster
|
gap_limit = 1 # make tests run faster
|
||||||
gap_limit_for_change = 1 # make tests run faster
|
gap_limit_for_change = 1 # make tests run faster
|
||||||
# TODO also use short gap limit for change addrs, for performance
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def check_seeded_keystore_sanity(cls, test_obj, ks):
|
def check_seeded_keystore_sanity(cls, test_obj, ks):
|
||||||
@@ -53,7 +54,7 @@ class WalletIntegrityHelper:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_standard_wallet(cls, ks, *, config: SimpleConfig, gap_limit=None, gap_limit_for_change=None):
|
def create_standard_wallet(cls, ks, *, config: SimpleConfig, gap_limit=None, gap_limit_for_change=None):
|
||||||
db = storage.WalletDB('', storage=None, upgrade=True)
|
db = WalletDB('', storage=None, upgrade=True)
|
||||||
db.put('keystore', ks.dump())
|
db.put('keystore', ks.dump())
|
||||||
db.put('gap_limit', gap_limit or cls.gap_limit)
|
db.put('gap_limit', gap_limit or cls.gap_limit)
|
||||||
db.put('gap_limit_for_change', gap_limit_for_change or cls.gap_limit_for_change)
|
db.put('gap_limit_for_change', gap_limit_for_change or cls.gap_limit_for_change)
|
||||||
@@ -63,7 +64,7 @@ class WalletIntegrityHelper:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_imported_wallet(cls, *, config: SimpleConfig, privkeys: bool):
|
def create_imported_wallet(cls, *, config: SimpleConfig, privkeys: bool):
|
||||||
db = storage.WalletDB('', storage=None, upgrade=True)
|
db = WalletDB('', storage=None, upgrade=True)
|
||||||
if privkeys:
|
if privkeys:
|
||||||
k = keystore.Imported_KeyStore({})
|
k = keystore.Imported_KeyStore({})
|
||||||
db.put('keystore', k.dump())
|
db.put('keystore', k.dump())
|
||||||
@@ -71,10 +72,18 @@ class WalletIntegrityHelper:
|
|||||||
return w
|
return w
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_multisig_wallet(cls, keystores: Sequence, multisig_type: str, *,
|
def create_multisig_wallet(
|
||||||
config: SimpleConfig, gap_limit=None, gap_limit_for_change=None):
|
cls,
|
||||||
|
keystores: Sequence,
|
||||||
|
multisig_type: str,
|
||||||
|
*,
|
||||||
|
config: SimpleConfig,
|
||||||
|
storage: WalletStorage | None = None,
|
||||||
|
gap_limit=None,
|
||||||
|
gap_limit_for_change=None,
|
||||||
|
):
|
||||||
"""Creates a multisig wallet."""
|
"""Creates a multisig wallet."""
|
||||||
db = storage.WalletDB('', storage=None, upgrade=False)
|
db = WalletDB('', storage=storage, upgrade=False)
|
||||||
for i, ks in enumerate(keystores):
|
for i, ks in enumerate(keystores):
|
||||||
cosigner_index = i + 1
|
cosigner_index = i + 1
|
||||||
db.put('x%d' % cosigner_index, ks.dump())
|
db.put('x%d' % cosigner_index, ks.dump())
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ from electrum.wallet import Abstract_Wallet
|
|||||||
from electrum import util
|
from electrum import util
|
||||||
from electrum import slip39
|
from electrum import slip39
|
||||||
from electrum.bip32 import KeyOriginInfo
|
from electrum.bip32 import KeyOriginInfo
|
||||||
|
from electrum import keystore
|
||||||
|
from electrum.storage import WalletStorage
|
||||||
|
|
||||||
from . import ElectrumTestCase
|
from . import ElectrumTestCase
|
||||||
from .test_wallet_vertical import UNICODE_HORROR
|
from .test_wallet_vertical import UNICODE_HORROR, WalletIntegrityHelper
|
||||||
|
|
||||||
|
|
||||||
class NetworkMock:
|
class NetworkMock:
|
||||||
@@ -125,8 +127,6 @@ class ServerConnectWizardTestCase(WizardTestCase):
|
|||||||
|
|
||||||
|
|
||||||
class KeystoreWizardTestCase(WizardTestCase):
|
class KeystoreWizardTestCase(WizardTestCase):
|
||||||
# TODO add test cases for:
|
|
||||||
# - multisig
|
|
||||||
|
|
||||||
class TKeystoreWizard(KeystoreWizard):
|
class TKeystoreWizard(KeystoreWizard):
|
||||||
def is_single_password(self):
|
def is_single_password(self):
|
||||||
@@ -390,6 +390,46 @@ class KeystoreWizardTestCase(WizardTestCase):
|
|||||||
wallet.disable_keystore(wallet.get_keystore())
|
wallet.disable_keystore(wallet.get_keystore())
|
||||||
self._sanity_checks_after_disabling_keystore(ks=wallet.get_keystore(), xpub=myxpub, key_origin_info=my_keyorigininfo)
|
self._sanity_checks_after_disabling_keystore(ks=wallet.get_keystore(), xpub=myxpub, key_origin_info=my_keyorigininfo)
|
||||||
|
|
||||||
|
async def test_multisig(self):
|
||||||
|
seed1 = "bitter grass shiver impose acquire brush forget axis eager alone wine silver"
|
||||||
|
xpub1 = "Zpub6ymNkfdyhypEoqQNNGAUz9gXeiWJsW8AWx8Aa6PnDdeL76UC9b1UPGmEvwWzzkVVghVQuDBry7CK7wCBBdysRQgFFmdDSqi5kWoZ3A4cBuA"
|
||||||
|
seed2 = "snow nest raise royal more walk demise rotate smooth spirit canyon gun"
|
||||||
|
xpub2 = "Zpub6xwgqLvc42wXB1wEELTdALD9iXwStMUkGqBgxkJFYumaL2dWgNvUkjEDWyDFZD3fZuDWDzd1KQJ4NwVHS7hs6H6QkpNYSShfNiUZsgMdtNg"
|
||||||
|
|
||||||
|
wallet = WalletIntegrityHelper.create_multisig_wallet(
|
||||||
|
[
|
||||||
|
keystore.from_seed(seed1, passphrase='', for_multisig=True),
|
||||||
|
keystore.from_xpub(xpub2),
|
||||||
|
],
|
||||||
|
'2of2',
|
||||||
|
config=self.config,
|
||||||
|
storage=WalletStorage(self.wallet_path),
|
||||||
|
)
|
||||||
|
|
||||||
|
w, v = self._wizard_for(wallet_type=wallet.wallet_type)
|
||||||
|
d = v.wizard_data
|
||||||
|
d.update({
|
||||||
|
'seed': seed2, 'seed_type': 'segwit', 'seed_extend': False, 'seed_variant': 'electrum',
|
||||||
|
})
|
||||||
|
self.assertTrue(w.is_last_view(v.view, d))
|
||||||
|
w.resolve_next(v.view, d)
|
||||||
|
ks, ishww = w._result
|
||||||
|
self.assertFalse(ishww)
|
||||||
|
self.assertEqual(ks.xpub, xpub2)
|
||||||
|
|
||||||
|
self.assertFalse(wallet.get_keystores()[0].is_watching_only())
|
||||||
|
self.assertTrue(wallet.get_keystores()[1].is_watching_only())
|
||||||
|
self.assertTrue(wallet.can_enable_disable_keystore(ks))
|
||||||
|
wallet.enable_keystore(ks, ishww, None)
|
||||||
|
self.assertFalse(wallet.get_keystores()[0].is_watching_only())
|
||||||
|
self.assertFalse(wallet.get_keystores()[1].is_watching_only())
|
||||||
|
self.assertEqual(seed1, wallet.get_keystores()[0].get_seed(None))
|
||||||
|
self.assertEqual(seed2, wallet.get_keystores()[1].get_seed(None))
|
||||||
|
|
||||||
|
keyorigininfo1 = wallet.get_keystores()[0].get_key_origin_info()
|
||||||
|
wallet.disable_keystore(wallet.get_keystores()[0])
|
||||||
|
self._sanity_checks_after_disabling_keystore(ks=wallet.get_keystores()[0], xpub=xpub1, key_origin_info=keyorigininfo1)
|
||||||
|
|
||||||
|
|
||||||
class WalletWizardTestCase(WizardTestCase):
|
class WalletWizardTestCase(WizardTestCase):
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user