diff --git a/electrum/base_wizard.py b/electrum/base_wizard.py index 7efd82297..838e6c4a6 100644 --- a/electrum/base_wizard.py +++ b/electrum/base_wizard.py @@ -417,7 +417,7 @@ class BaseWizard(object): self.passphrase_dialog(run_next=f, is_restoring=True) if is_ext else f('') elif self.seed_type == 'old': self.run('create_keystore', seed, '') - elif self.seed_type == '2fa': + elif bitcoin.is_any_2fa_seed_type(self.seed_type): self.load_2fa() self.run('on_restore_seed', seed, is_ext) else: @@ -540,18 +540,20 @@ class BaseWizard(object): def show_xpub_and_add_cosigners(self, xpub): self.show_xpub_dialog(xpub=xpub, run_next=lambda x: self.run('choose_keystore')) - def choose_seed_type(self): + def choose_seed_type(self, message=None, choices=None): title = _('Choose Seed type') - message = ' '.join([ - _("The type of addresses used by your wallet will depend on your seed."), - _("Segwit wallets use bech32 addresses, defined in BIP173."), - _("Please note that websites and other wallets may not support these addresses yet."), - _("Thus, you might want to keep using a non-segwit wallet in order to be able to receive bitcoins during the transition period.") - ]) - choices = [ - ('create_segwit_seed', _('Segwit')), - ('create_standard_seed', _('Legacy')), - ] + if message is None: + message = ' '.join([ + _("The type of addresses used by your wallet will depend on your seed."), + _("Segwit wallets use bech32 addresses, defined in BIP173."), + _("Please note that websites and other wallets may not support these addresses yet."), + _("Thus, you might want to keep using a non-segwit wallet in order to be able to receive bitcoins during the transition period.") + ]) + if choices is None: + choices = [ + ('create_segwit_seed', _('Segwit')), + ('create_standard_seed', _('Legacy')), + ] self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run) def create_segwit_seed(self): self.create_seed('segwit') diff --git a/electrum/bitcoin.py b/electrum/bitcoin.py index 194ccfa78..ee1b6c0fb 100644 --- a/electrum/bitcoin.py +++ b/electrum/bitcoin.py @@ -207,6 +207,8 @@ def seed_type(x: str) -> str: return 'segwit' elif is_new_seed(x, version.SEED_PREFIX_2FA): return '2fa' + elif is_new_seed(x, version.SEED_PREFIX_2FA_SW): + return '2fa_segwit' return '' @@ -214,6 +216,10 @@ def is_seed(x: str) -> bool: return bool(seed_type(x)) +def is_any_2fa_seed_type(seed_type): + return seed_type in ['2fa', '2fa_segwit'] + + ############ functions from pywallet ##################### def hash160_to_b58_address(h160: bytes, addrtype: int) -> str: diff --git a/electrum/plugins/trustedcoin/trustedcoin.py b/electrum/plugins/trustedcoin/trustedcoin.py index 03a7ca832..b3b2d0a99 100644 --- a/electrum/plugins/trustedcoin/trustedcoin.py +++ b/electrum/plugins/trustedcoin/trustedcoin.py @@ -34,9 +34,9 @@ from urllib.parse import quote from aiohttp import ClientResponse from electrum import ecc, constants, keystore, version, bip32 -from electrum.bitcoin import TYPE_ADDRESS, is_new_seed, public_key_to_p2pkh +from electrum.bitcoin import TYPE_ADDRESS, is_new_seed, public_key_to_p2pkh, seed_type, is_any_2fa_seed_type from electrum.bip32 import (deserialize_xpub, deserialize_xprv, bip32_private_key, CKD_pub, - serialize_xpub, bip32_root, bip32_private_derivation) + serialize_xpub, bip32_root, bip32_private_derivation, xpub_type) from electrum.crypto import sha256 from electrum.transaction import TxOutput from electrum.mnemonic import Mnemonic @@ -47,12 +47,20 @@ from electrum.util import NotEnoughFunds from electrum.storage import STO_EV_USER_PW from electrum.network import Network -# signing_xpub is hardcoded so that the wallet can be restored from seed, without TrustedCoin's server -def get_signing_xpub(): - if constants.net.TESTNET: - return "tpubD6NzVbkrYhZ4XdmyJQcCPjQfg6RXVUzGFhPjZ7uvRC8JLcS7Hw1i7UTpyhp9grHpak4TyK2hzBJrujDVLXQ6qB5tNpVx9rC6ixijUXadnmY" +def get_signing_xpub(xtype): + if xtype == 'standard': + if constants.net.TESTNET: + return "tpubD6NzVbkrYhZ4XdmyJQcCPjQfg6RXVUzGFhPjZ7uvRC8JLcS7Hw1i7UTpyhp9grHpak4TyK2hzBJrujDVLXQ6qB5tNpVx9rC6ixijUXadnmY" + else: + return "xpub661MyMwAqRbcGnMkaTx2594P9EDuiEqMq25PM2aeG6UmwzaohgA6uDmNsvSUV8ubqwA3Wpste1hg69XHgjUuCD5HLcEp2QPzyV1HMrPppsL" + elif xtype == 'p2wsh': + # TODO these are temp xpubs + if constants.net.TESTNET: + return "Vpub5fcdcgEwTJmbmqAktuK8Kyq92fMf7sWkcP6oqAii2tG47dNbfkGEGUbfS9NuZaRywLkHE6EmUksrqo32ZL3ouLN1HTar6oRiHpDzKMAF1tf" + else: + return "Zpub6xwgqLvc42wXB1wEELTdALD9iXwStMUkGqBgxkJFYumaL2dWgNvUkjEDWyDFZD3fZuDWDzd1KQJ4NwVHS7hs6H6QkpNYSShfNiUZsgMdtNg" else: - return "xpub661MyMwAqRbcGnMkaTx2594P9EDuiEqMq25PM2aeG6UmwzaohgA6uDmNsvSUV8ubqwA3Wpste1hg69XHgjUuCD5HLcEp2QPzyV1HMrPppsL" + raise NotImplementedError('xtype: {}'.format(xtype)) def get_billing_xpub(): if constants.net.TESTNET: @@ -60,7 +68,6 @@ def get_billing_xpub(): else: return "xpub6DTBdtBB8qUmH5c77v8qVGVoYk7WjJNpGvutqjLasNG1mbux6KsojaLrYf2sRhXAVU4NaFuHhbD9SvVPRt1MB1MaMooRuhHcAZH1yhQ1qDU" -SEED_PREFIX = version.SEED_PREFIX_2FA DISCLAIMER = [ _("Two-factor authentication is a service provided by TrustedCoin. " @@ -377,7 +384,8 @@ class TrustedCoinPlugin(BasePlugin): @staticmethod def is_valid_seed(seed): - return is_new_seed(seed, SEED_PREFIX) + t = seed_type(seed) + return is_any_2fa_seed_type(t) def is_available(self): return True @@ -449,8 +457,10 @@ class TrustedCoinPlugin(BasePlugin): t.start() return t - def make_seed(self): - return Mnemonic('english').make_seed(seed_type='2fa', num_bits=128) + def make_seed(self, seed_type): + if not is_any_2fa_seed_type(seed_type): + raise BaseException('unexpected seed type: {}'.format(seed_type)) + return Mnemonic('english').make_seed(seed_type=seed_type, num_bits=128) @hook def do_clear(self, window): @@ -465,25 +475,41 @@ class TrustedCoinPlugin(BasePlugin): title = _('Create or restore') message = _('Do you want to create a new seed, or to restore a wallet using an existing seed?') choices = [ - ('create_seed', _('Create a new seed')), + ('choose_seed_type', _('Create a new seed')), ('restore_wallet', _('I already have a seed')), ] wizard.choice_dialog(title=title, message=message, choices=choices, run_next=wizard.run) - def create_seed(self, wizard): - seed = self.make_seed() + def choose_seed_type(self, wizard): + choices = [ + ('create_2fa_seed', _('Standard 2FA')), + ('create_2fa_segwit_seed', _('Segwit 2FA')), + ] + wizard.choose_seed_type(choices=choices) + + def create_2fa_seed(self, wizard): self.create_seed(wizard, '2fa') + def create_2fa_segwit_seed(self, wizard): self.create_seed(wizard, '2fa_segwit') + + def create_seed(self, wizard, seed_type): + seed = self.make_seed(seed_type) f = lambda x: wizard.request_passphrase(seed, x) wizard.show_seed_dialog(run_next=f, seed_text=seed) @classmethod def get_xkeys(self, seed, passphrase, derivation): + t = seed_type(seed) + assert is_any_2fa_seed_type(t) + xtype = 'standard' if t == '2fa' else 'p2wsh' bip32_seed = Mnemonic.mnemonic_to_seed(seed, passphrase) - xprv, xpub = bip32_root(bip32_seed, 'standard') + xprv, xpub = bip32_root(bip32_seed, xtype) xprv, xpub = bip32_private_derivation(xprv, "m/", derivation) return xprv, xpub @classmethod def xkeys_from_seed(self, seed, passphrase): + t = seed_type(seed) + if not is_any_2fa_seed_type(t): + raise BaseException('unexpected seed type: {}'.format(t)) words = seed.split() n = len(words) # old version use long seed phrases @@ -495,7 +521,7 @@ class TrustedCoinPlugin(BasePlugin): raise Exception('old 2fa seed cannot have passphrase') xprv1, xpub1 = self.get_xkeys(' '.join(words[0:12]), '', "m/") xprv2, xpub2 = self.get_xkeys(' '.join(words[12:]), '', "m/") - elif n==12: + elif not t == '2fa' or n == 12: xprv1, xpub1 = self.get_xkeys(seed, passphrase, "m/0'/") xprv2, xpub2 = self.get_xkeys(seed, passphrase, "m/1'/") else: @@ -561,7 +587,8 @@ class TrustedCoinPlugin(BasePlugin): storage.put('x1/', k1.dump()) storage.put('x2/', k2.dump()) long_user_id, short_id = get_user_id(storage) - xpub3 = make_xpub(get_signing_xpub(), long_user_id) + xtype = xpub_type(xpub1) + xpub3 = make_xpub(get_signing_xpub(xtype), long_user_id) k3 = keystore.from_xpub(xpub3) storage.put('x3/', k3.dump()) @@ -578,7 +605,8 @@ class TrustedCoinPlugin(BasePlugin): xpub2 = wizard.storage.get('x2/')['xpub'] # Generate third key deterministically. long_user_id, short_id = get_user_id(wizard.storage) - xpub3 = make_xpub(get_signing_xpub(), long_user_id) + xtype = xpub_type(xpub1) + xpub3 = make_xpub(get_signing_xpub(xtype), long_user_id) # secret must be sent by the server try: r = server.create(xpub1, xpub2, email) diff --git a/electrum/tests/test_wallet_vertical.py b/electrum/tests/test_wallet_vertical.py index 9bc1a6e89..0acc2776c 100644 --- a/electrum/tests/test_wallet_vertical.py +++ b/electrum/tests/test_wallet_vertical.py @@ -178,7 +178,8 @@ class TestWalletKeystoreAddressIntegrityForMainnet(SequentialTestCase): 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) + xtype = bitcoin.xpub_type(xpub1) + xpub3 = trustedcoin.make_xpub(trustedcoin.get_signing_xpub(xtype), long_user_id) ks3 = keystore.from_xpub(xpub3) WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks3) self.assertTrue(isinstance(ks3, keystore.BIP32_KeyStore)) diff --git a/electrum/version.py b/electrum/version.py index 5866941f3..31ecebe5c 100644 --- a/electrum/version.py +++ b/electrum/version.py @@ -4,9 +4,10 @@ APK_VERSION = '3.3.0.0' # read by buildozer.spec PROTOCOL_VERSION = '1.4' # protocol version requested # The hash of the mnemonic seed must begin with this -SEED_PREFIX = '01' # Standard wallet -SEED_PREFIX_2FA = '101' # Two-factor authentication -SEED_PREFIX_SW = '100' # Segwit wallet +SEED_PREFIX = '01' # Standard wallet +SEED_PREFIX_SW = '100' # Segwit wallet +SEED_PREFIX_2FA = '101' # Two-factor authentication +SEED_PREFIX_2FA_SW = '102' # Two-factor auth, using segwit def seed_prefix(seed_type): @@ -16,3 +17,5 @@ def seed_prefix(seed_type): return SEED_PREFIX_SW elif seed_type == '2fa': return SEED_PREFIX_2FA + elif seed_type == '2fa_segwit': + return SEED_PREFIX_2FA_SW