2fa segwit (from ghost43's PR)

This commit is contained in:
ThomasV 2018-11-28 16:24:18 +01:00
parent 0ec7005f90
commit 5a93bf054e
5 changed files with 74 additions and 34 deletions

View file

@ -417,7 +417,7 @@ class BaseWizard(object):
self.passphrase_dialog(run_next=f, is_restoring=True) if is_ext else f('') self.passphrase_dialog(run_next=f, is_restoring=True) if is_ext else f('')
elif self.seed_type == 'old': elif self.seed_type == 'old':
self.run('create_keystore', seed, '') self.run('create_keystore', seed, '')
elif self.seed_type == '2fa': elif bitcoin.is_any_2fa_seed_type(self.seed_type):
self.load_2fa() self.load_2fa()
self.run('on_restore_seed', seed, is_ext) self.run('on_restore_seed', seed, is_ext)
else: else:
@ -540,18 +540,20 @@ class BaseWizard(object):
def show_xpub_and_add_cosigners(self, xpub): def show_xpub_and_add_cosigners(self, xpub):
self.show_xpub_dialog(xpub=xpub, run_next=lambda x: self.run('choose_keystore')) 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') title = _('Choose Seed type')
message = ' '.join([ if message is None:
_("The type of addresses used by your wallet will depend on your seed."), message = ' '.join([
_("Segwit wallets use bech32 addresses, defined in BIP173."), _("The type of addresses used by your wallet will depend on your seed."),
_("Please note that websites and other wallets may not support these addresses yet."), _("Segwit wallets use bech32 addresses, defined in BIP173."),
_("Thus, you might want to keep using a non-segwit wallet in order to be able to receive bitcoins during the transition period.") _("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')), if choices is None:
('create_standard_seed', _('Legacy')), choices = [
] ('create_segwit_seed', _('Segwit')),
('create_standard_seed', _('Legacy')),
]
self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run) self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run)
def create_segwit_seed(self): self.create_seed('segwit') def create_segwit_seed(self): self.create_seed('segwit')

View file

@ -207,6 +207,8 @@ def seed_type(x: str) -> str:
return 'segwit' return 'segwit'
elif is_new_seed(x, version.SEED_PREFIX_2FA): elif is_new_seed(x, version.SEED_PREFIX_2FA):
return '2fa' return '2fa'
elif is_new_seed(x, version.SEED_PREFIX_2FA_SW):
return '2fa_segwit'
return '' return ''
@ -214,6 +216,10 @@ def is_seed(x: str) -> bool:
return bool(seed_type(x)) return bool(seed_type(x))
def is_any_2fa_seed_type(seed_type):
return seed_type in ['2fa', '2fa_segwit']
############ functions from pywallet ##################### ############ functions from pywallet #####################
def hash160_to_b58_address(h160: bytes, addrtype: int) -> str: def hash160_to_b58_address(h160: bytes, addrtype: int) -> str:

View file

@ -34,9 +34,9 @@ from urllib.parse import quote
from aiohttp import ClientResponse from aiohttp import ClientResponse
from electrum import ecc, constants, keystore, version, bip32 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, 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.crypto import sha256
from electrum.transaction import TxOutput from electrum.transaction import TxOutput
from electrum.mnemonic import Mnemonic from electrum.mnemonic import Mnemonic
@ -47,12 +47,20 @@ from electrum.util import NotEnoughFunds
from electrum.storage import STO_EV_USER_PW from electrum.storage import STO_EV_USER_PW
from electrum.network import Network 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(xtype):
def get_signing_xpub(): if xtype == 'standard':
if constants.net.TESTNET: if constants.net.TESTNET:
return "tpubD6NzVbkrYhZ4XdmyJQcCPjQfg6RXVUzGFhPjZ7uvRC8JLcS7Hw1i7UTpyhp9grHpak4TyK2hzBJrujDVLXQ6qB5tNpVx9rC6ixijUXadnmY" return "tpubD6NzVbkrYhZ4XdmyJQcCPjQfg6RXVUzGFhPjZ7uvRC8JLcS7Hw1i7UTpyhp9grHpak4TyK2hzBJrujDVLXQ6qB5tNpVx9rC6ixijUXadnmY"
else:
return "xpub661MyMwAqRbcGnMkaTx2594P9EDuiEqMq25PM2aeG6UmwzaohgA6uDmNsvSUV8ubqwA3Wpste1hg69XHgjUuCD5HLcEp2QPzyV1HMrPppsL"
elif xtype == 'p2wsh':
# TODO these are temp xpubs
if constants.net.TESTNET:
return "Vpub5fcdcgEwTJmbmqAktuK8Kyq92fMf7sWkcP6oqAii2tG47dNbfkGEGUbfS9NuZaRywLkHE6EmUksrqo32ZL3ouLN1HTar6oRiHpDzKMAF1tf"
else:
return "Zpub6xwgqLvc42wXB1wEELTdALD9iXwStMUkGqBgxkJFYumaL2dWgNvUkjEDWyDFZD3fZuDWDzd1KQJ4NwVHS7hs6H6QkpNYSShfNiUZsgMdtNg"
else: else:
return "xpub661MyMwAqRbcGnMkaTx2594P9EDuiEqMq25PM2aeG6UmwzaohgA6uDmNsvSUV8ubqwA3Wpste1hg69XHgjUuCD5HLcEp2QPzyV1HMrPppsL" raise NotImplementedError('xtype: {}'.format(xtype))
def get_billing_xpub(): def get_billing_xpub():
if constants.net.TESTNET: if constants.net.TESTNET:
@ -60,7 +68,6 @@ def get_billing_xpub():
else: else:
return "xpub6DTBdtBB8qUmH5c77v8qVGVoYk7WjJNpGvutqjLasNG1mbux6KsojaLrYf2sRhXAVU4NaFuHhbD9SvVPRt1MB1MaMooRuhHcAZH1yhQ1qDU" return "xpub6DTBdtBB8qUmH5c77v8qVGVoYk7WjJNpGvutqjLasNG1mbux6KsojaLrYf2sRhXAVU4NaFuHhbD9SvVPRt1MB1MaMooRuhHcAZH1yhQ1qDU"
SEED_PREFIX = version.SEED_PREFIX_2FA
DISCLAIMER = [ DISCLAIMER = [
_("Two-factor authentication is a service provided by TrustedCoin. " _("Two-factor authentication is a service provided by TrustedCoin. "
@ -377,7 +384,8 @@ class TrustedCoinPlugin(BasePlugin):
@staticmethod @staticmethod
def is_valid_seed(seed): 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): def is_available(self):
return True return True
@ -449,8 +457,10 @@ class TrustedCoinPlugin(BasePlugin):
t.start() t.start()
return t return t
def make_seed(self): def make_seed(self, seed_type):
return Mnemonic('english').make_seed(seed_type='2fa', num_bits=128) 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 @hook
def do_clear(self, window): def do_clear(self, window):
@ -465,25 +475,41 @@ class TrustedCoinPlugin(BasePlugin):
title = _('Create or restore') title = _('Create or restore')
message = _('Do you want to create a new seed, or to restore a wallet using an existing seed?') message = _('Do you want to create a new seed, or to restore a wallet using an existing seed?')
choices = [ choices = [
('create_seed', _('Create a new seed')), ('choose_seed_type', _('Create a new seed')),
('restore_wallet', _('I already have a seed')), ('restore_wallet', _('I already have a seed')),
] ]
wizard.choice_dialog(title=title, message=message, choices=choices, run_next=wizard.run) wizard.choice_dialog(title=title, message=message, choices=choices, run_next=wizard.run)
def create_seed(self, wizard): def choose_seed_type(self, wizard):
seed = self.make_seed() 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) f = lambda x: wizard.request_passphrase(seed, x)
wizard.show_seed_dialog(run_next=f, seed_text=seed) wizard.show_seed_dialog(run_next=f, seed_text=seed)
@classmethod @classmethod
def get_xkeys(self, seed, passphrase, derivation): 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) 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) xprv, xpub = bip32_private_derivation(xprv, "m/", derivation)
return xprv, xpub return xprv, xpub
@classmethod @classmethod
def xkeys_from_seed(self, seed, passphrase): 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() words = seed.split()
n = len(words) n = len(words)
# old version use long seed phrases # old version use long seed phrases
@ -495,7 +521,7 @@ class TrustedCoinPlugin(BasePlugin):
raise Exception('old 2fa seed cannot have passphrase') raise Exception('old 2fa seed cannot have passphrase')
xprv1, xpub1 = self.get_xkeys(' '.join(words[0:12]), '', "m/") xprv1, xpub1 = self.get_xkeys(' '.join(words[0:12]), '', "m/")
xprv2, xpub2 = self.get_xkeys(' '.join(words[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'/") xprv1, xpub1 = self.get_xkeys(seed, passphrase, "m/0'/")
xprv2, xpub2 = self.get_xkeys(seed, passphrase, "m/1'/") xprv2, xpub2 = self.get_xkeys(seed, passphrase, "m/1'/")
else: else:
@ -561,7 +587,8 @@ class TrustedCoinPlugin(BasePlugin):
storage.put('x1/', k1.dump()) storage.put('x1/', k1.dump())
storage.put('x2/', k2.dump()) storage.put('x2/', k2.dump())
long_user_id, short_id = get_user_id(storage) 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) k3 = keystore.from_xpub(xpub3)
storage.put('x3/', k3.dump()) storage.put('x3/', k3.dump())
@ -578,7 +605,8 @@ class TrustedCoinPlugin(BasePlugin):
xpub2 = wizard.storage.get('x2/')['xpub'] xpub2 = wizard.storage.get('x2/')['xpub']
# Generate third key deterministically. # Generate third key deterministically.
long_user_id, short_id = get_user_id(wizard.storage) 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 # secret must be sent by the server
try: try:
r = server.create(xpub1, xpub2, email) r = server.create(xpub1, xpub2, email)

View file

@ -178,7 +178,8 @@ class TestWalletKeystoreAddressIntegrityForMainnet(SequentialTestCase):
long_user_id, short_id = trustedcoin.get_user_id( long_user_id, short_id = trustedcoin.get_user_id(
{'x1/': {'xpub': xpub1}, {'x1/': {'xpub': xpub1},
'x2/': {'xpub': xpub2}}) '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) ks3 = keystore.from_xpub(xpub3)
WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks3) WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks3)
self.assertTrue(isinstance(ks3, keystore.BIP32_KeyStore)) self.assertTrue(isinstance(ks3, keystore.BIP32_KeyStore))

View file

@ -4,9 +4,10 @@ APK_VERSION = '3.3.0.0' # read by buildozer.spec
PROTOCOL_VERSION = '1.4' # protocol version requested PROTOCOL_VERSION = '1.4' # protocol version requested
# The hash of the mnemonic seed must begin with this # The hash of the mnemonic seed must begin with this
SEED_PREFIX = '01' # Standard wallet SEED_PREFIX = '01' # Standard wallet
SEED_PREFIX_2FA = '101' # Two-factor authentication SEED_PREFIX_SW = '100' # Segwit 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): def seed_prefix(seed_type):
@ -16,3 +17,5 @@ def seed_prefix(seed_type):
return SEED_PREFIX_SW return SEED_PREFIX_SW
elif seed_type == '2fa': elif seed_type == '2fa':
return SEED_PREFIX_2FA return SEED_PREFIX_2FA
elif seed_type == '2fa_segwit':
return SEED_PREFIX_2FA_SW