mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-08-30 17:01:34 +00:00
Merge pull request #4880 from spesmilo/2fa_segwit
2fa segwit (from ghost43's PR)
This commit is contained in:
commit
dd848304e6
7 changed files with 122 additions and 78 deletions
|
@ -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')
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -195,18 +195,6 @@ class Plugin(TrustedCoinPlugin):
|
||||||
vbox.addLayout(Buttons(CloseButton(d)))
|
vbox.addLayout(Buttons(CloseButton(d)))
|
||||||
d.exec_()
|
d.exec_()
|
||||||
|
|
||||||
def on_buy(self, window, k, v, d):
|
|
||||||
d.close()
|
|
||||||
if window.pluginsdialog:
|
|
||||||
window.pluginsdialog.close()
|
|
||||||
wallet = window.wallet
|
|
||||||
uri = "bitcoin:" + wallet.billing_info['billing_address'] + "?message=TrustedCoin %d Prepaid Transactions&amount="%k + str(Decimal(v)/100000000)
|
|
||||||
wallet.is_billing = True
|
|
||||||
window.pay_to_URI(uri)
|
|
||||||
window.payto_e.setFrozen(True)
|
|
||||||
window.message_e.setFrozen(True)
|
|
||||||
window.amount_e.setFrozen(True)
|
|
||||||
|
|
||||||
def go_online_dialog(self, wizard):
|
def go_online_dialog(self, wizard):
|
||||||
msg = [
|
msg = [
|
||||||
_("Your wallet file is: {}.").format(os.path.abspath(wizard.storage.path)),
|
_("Your wallet file is: {}.").format(os.path.abspath(wizard.storage.path)),
|
||||||
|
|
|
@ -28,15 +28,17 @@ import json
|
||||||
import base64
|
import base64
|
||||||
import time
|
import time
|
||||||
import hashlib
|
import hashlib
|
||||||
|
from collections import defaultdict
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
from urllib.parse import quote
|
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, bitcoin
|
||||||
from electrum.bitcoin import TYPE_ADDRESS, is_new_seed, public_key_to_p2pkh
|
from electrum.bitcoin import TYPE_ADDRESS, is_new_seed, 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 +49,18 @@ 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 not constants.net.TESTNET:
|
||||||
if constants.net.TESTNET:
|
xpub = "xpub661MyMwAqRbcGnMkaTx2594P9EDuiEqMq25PM2aeG6UmwzaohgA6uDmNsvSUV8ubqwA3Wpste1hg69XHgjUuCD5HLcEp2QPzyV1HMrPppsL"
|
||||||
return "tpubD6NzVbkrYhZ4XdmyJQcCPjQfg6RXVUzGFhPjZ7uvRC8JLcS7Hw1i7UTpyhp9grHpak4TyK2hzBJrujDVLXQ6qB5tNpVx9rC6ixijUXadnmY"
|
|
||||||
else:
|
else:
|
||||||
return "xpub661MyMwAqRbcGnMkaTx2594P9EDuiEqMq25PM2aeG6UmwzaohgA6uDmNsvSUV8ubqwA3Wpste1hg69XHgjUuCD5HLcEp2QPzyV1HMrPppsL"
|
xpub = "tpubD6NzVbkrYhZ4XdmyJQcCPjQfg6RXVUzGFhPjZ7uvRC8JLcS7Hw1i7UTpyhp9grHpak4TyK2hzBJrujDVLXQ6qB5tNpVx9rC6ixijUXadnmY"
|
||||||
|
if xtype not in ('standard', 'p2wsh'):
|
||||||
|
raise NotImplementedError('xtype: {}'.format(xtype))
|
||||||
|
if xtype == 'standard':
|
||||||
|
return xpub
|
||||||
|
_, depth, fingerprint, child_number, c, cK = bip32.deserialize_xpub(xpub)
|
||||||
|
xpub = bip32.serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)
|
||||||
|
return xpub
|
||||||
|
|
||||||
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. "
|
||||||
|
@ -239,12 +246,18 @@ class Wallet_2fa(Multisig_Wallet):
|
||||||
self._load_billing_addresses()
|
self._load_billing_addresses()
|
||||||
|
|
||||||
def _load_billing_addresses(self):
|
def _load_billing_addresses(self):
|
||||||
billing_addresses = self.storage.get('trustedcoin_billing_addresses', {})
|
billing_addresses = {
|
||||||
self._billing_addresses = {} # index -> addr
|
'legacy': self.storage.get('trustedcoin_billing_addresses', {}),
|
||||||
# convert keys from str to int
|
'segwit': self.storage.get('trustedcoin_billing_addresses_segwit', {})
|
||||||
for index, addr in list(billing_addresses.items()):
|
}
|
||||||
self._billing_addresses[int(index)] = addr
|
self._billing_addresses = {} # type: Dict[str, Dict[int, str]] # addr_type -> index -> addr
|
||||||
self._billing_addresses_set = set(self._billing_addresses.values()) # set of addrs
|
self._billing_addresses_set = set() # set of addrs
|
||||||
|
for addr_type, d in list(billing_addresses.items()):
|
||||||
|
self._billing_addresses[addr_type] = {}
|
||||||
|
# convert keys from str to int
|
||||||
|
for index, addr in d.items():
|
||||||
|
self._billing_addresses[addr_type][int(index)] = addr
|
||||||
|
self._billing_addresses_set.add(addr)
|
||||||
|
|
||||||
def can_sign_without_server(self):
|
def can_sign_without_server(self):
|
||||||
return not self.keystores['x2/'].is_watching_only()
|
return not self.keystores['x2/'].is_watching_only()
|
||||||
|
@ -284,7 +297,7 @@ class Wallet_2fa(Multisig_Wallet):
|
||||||
self, coins, o, config, fixed_fee, change_addr)
|
self, coins, o, config, fixed_fee, change_addr)
|
||||||
fee = self.extra_fee(config) if not is_sweep else 0
|
fee = self.extra_fee(config) if not is_sweep else 0
|
||||||
if fee:
|
if fee:
|
||||||
address = self.billing_info['billing_address']
|
address = self.billing_info['billing_address_segwit']
|
||||||
fee_output = TxOutput(TYPE_ADDRESS, address, fee)
|
fee_output = TxOutput(TYPE_ADDRESS, address, fee)
|
||||||
try:
|
try:
|
||||||
tx = mk_tx(outputs + [fee_output])
|
tx = mk_tx(outputs + [fee_output])
|
||||||
|
@ -305,7 +318,7 @@ class Wallet_2fa(Multisig_Wallet):
|
||||||
return
|
return
|
||||||
otp = int(otp)
|
otp = int(otp)
|
||||||
long_user_id, short_id = self.get_user_id()
|
long_user_id, short_id = self.get_user_id()
|
||||||
raw_tx = tx.serialize_to_network()
|
raw_tx = tx.serialize()
|
||||||
r = server.sign(short_id, raw_tx, otp)
|
r = server.sign(short_id, raw_tx, otp)
|
||||||
if r:
|
if r:
|
||||||
raw_tx = r.get('transaction')
|
raw_tx = r.get('transaction')
|
||||||
|
@ -315,8 +328,9 @@ class Wallet_2fa(Multisig_Wallet):
|
||||||
self.billing_info = None
|
self.billing_info = None
|
||||||
self.plugin.start_request_thread(self)
|
self.plugin.start_request_thread(self)
|
||||||
|
|
||||||
def add_new_billing_address(self, billing_index: int, address: str):
|
def add_new_billing_address(self, billing_index: int, address: str, addr_type: str):
|
||||||
saved_addr = self._billing_addresses.get(billing_index)
|
billing_addresses_of_this_type = self._billing_addresses[addr_type]
|
||||||
|
saved_addr = billing_addresses_of_this_type.get(billing_index)
|
||||||
if saved_addr is not None:
|
if saved_addr is not None:
|
||||||
if saved_addr == address:
|
if saved_addr == address:
|
||||||
return # already saved this address
|
return # already saved this address
|
||||||
|
@ -325,16 +339,18 @@ class Wallet_2fa(Multisig_Wallet):
|
||||||
'for index {}, already saved {}, now got {}'
|
'for index {}, already saved {}, now got {}'
|
||||||
.format(billing_index, saved_addr, address))
|
.format(billing_index, saved_addr, address))
|
||||||
# do we have all prior indices? (are we synced?)
|
# do we have all prior indices? (are we synced?)
|
||||||
largest_index_we_have = max(self._billing_addresses) if self._billing_addresses else -1
|
largest_index_we_have = max(billing_addresses_of_this_type) if billing_addresses_of_this_type else -1
|
||||||
if largest_index_we_have + 1 < billing_index: # need to sync
|
if largest_index_we_have + 1 < billing_index: # need to sync
|
||||||
for i in range(largest_index_we_have + 1, billing_index):
|
for i in range(largest_index_we_have + 1, billing_index):
|
||||||
addr = make_billing_address(self, i)
|
addr = make_billing_address(self, i, addr_type=addr_type)
|
||||||
self._billing_addresses[i] = addr
|
billing_addresses_of_this_type[i] = addr
|
||||||
self._billing_addresses_set.add(addr)
|
self._billing_addresses_set.add(addr)
|
||||||
# save this address; and persist to disk
|
# save this address; and persist to disk
|
||||||
self._billing_addresses[billing_index] = address
|
billing_addresses_of_this_type[billing_index] = address
|
||||||
self._billing_addresses_set.add(address)
|
self._billing_addresses_set.add(address)
|
||||||
self.storage.put('trustedcoin_billing_addresses', self._billing_addresses)
|
self._billing_addresses[addr_type] = billing_addresses_of_this_type
|
||||||
|
self.storage.put('trustedcoin_billing_addresses', self._billing_addresses['legacy'])
|
||||||
|
self.storage.put('trustedcoin_billing_addresses_segwit', self._billing_addresses['segwit'])
|
||||||
# FIXME this often runs in a daemon thread, where storage.write will fail
|
# FIXME this often runs in a daemon thread, where storage.write will fail
|
||||||
self.storage.write()
|
self.storage.write()
|
||||||
|
|
||||||
|
@ -358,12 +374,17 @@ def make_xpub(xpub, s):
|
||||||
cK2, c2 = bip32._CKD_pub(cK, c, s)
|
cK2, c2 = bip32._CKD_pub(cK, c, s)
|
||||||
return serialize_xpub(version, c2, cK2)
|
return serialize_xpub(version, c2, cK2)
|
||||||
|
|
||||||
def make_billing_address(wallet, num):
|
def make_billing_address(wallet, num, addr_type):
|
||||||
long_id, short_id = wallet.get_user_id()
|
long_id, short_id = wallet.get_user_id()
|
||||||
xpub = make_xpub(get_billing_xpub(), long_id)
|
xpub = make_xpub(get_billing_xpub(), long_id)
|
||||||
version, _, _, _, c, cK = deserialize_xpub(xpub)
|
version, _, _, _, c, cK = deserialize_xpub(xpub)
|
||||||
cK, c = CKD_pub(cK, c, num)
|
cK, c = CKD_pub(cK, c, num)
|
||||||
return public_key_to_p2pkh(cK)
|
if addr_type == 'legacy':
|
||||||
|
return bitcoin.public_key_to_p2pkh(cK)
|
||||||
|
elif addr_type == 'segwit':
|
||||||
|
return bitcoin.public_key_to_p2wpkh(cK)
|
||||||
|
else:
|
||||||
|
raise ValueError(f'unexpected billing type: {addr_type}')
|
||||||
|
|
||||||
|
|
||||||
class TrustedCoinPlugin(BasePlugin):
|
class TrustedCoinPlugin(BasePlugin):
|
||||||
|
@ -377,7 +398,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
|
||||||
|
@ -420,7 +442,7 @@ class TrustedCoinPlugin(BasePlugin):
|
||||||
return f
|
return f
|
||||||
|
|
||||||
@finish_requesting
|
@finish_requesting
|
||||||
def request_billing_info(self, wallet):
|
def request_billing_info(self, wallet: 'Wallet_2fa'):
|
||||||
if wallet.can_sign_without_server():
|
if wallet.can_sign_without_server():
|
||||||
return
|
return
|
||||||
self.print_error("request billing info")
|
self.print_error("request billing info")
|
||||||
|
@ -430,11 +452,16 @@ class TrustedCoinPlugin(BasePlugin):
|
||||||
self.print_error('cannot connect to TrustedCoin server: {}'.format(repr(e)))
|
self.print_error('cannot connect to TrustedCoin server: {}'.format(repr(e)))
|
||||||
return
|
return
|
||||||
billing_index = billing_info['billing_index']
|
billing_index = billing_info['billing_index']
|
||||||
billing_address = make_billing_address(wallet, billing_index)
|
# add segwit billing address; this will be used for actual billing
|
||||||
if billing_address != billing_info['billing_address']:
|
billing_address = make_billing_address(wallet, billing_index, addr_type='segwit')
|
||||||
raise Exception('unexpected trustedcoin billing address: expected {}, received {}'
|
if billing_address != billing_info['billing_address_segwit']:
|
||||||
.format(billing_address, billing_info['billing_address']))
|
raise Exception(f'unexpected trustedcoin billing address: '
|
||||||
wallet.add_new_billing_address(billing_index, billing_address)
|
f'calculated {billing_address}, received {billing_info["billing_address_segwit"]}')
|
||||||
|
wallet.add_new_billing_address(billing_index, billing_address, addr_type='segwit')
|
||||||
|
# also add legacy billing address; only used for detecting past payments in GUI
|
||||||
|
billing_address = make_billing_address(wallet, billing_index, addr_type='legacy')
|
||||||
|
wallet.add_new_billing_address(billing_index, billing_address, addr_type='legacy')
|
||||||
|
|
||||||
wallet.billing_info = billing_info
|
wallet.billing_info = billing_info
|
||||||
wallet.price_per_tx = dict(billing_info['price_per_tx'])
|
wallet.price_per_tx = dict(billing_info['price_per_tx'])
|
||||||
wallet.price_per_tx.pop(1, None)
|
wallet.price_per_tx.pop(1, None)
|
||||||
|
@ -449,8 +476,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 +494,40 @@ 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_segwit_seed', _('Segwit 2FA')),
|
||||||
|
('create_2fa_seed', _('Legacy 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, t, passphrase, derivation):
|
||||||
|
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
|
||||||
|
@ -493,11 +537,11 @@ class TrustedCoinPlugin(BasePlugin):
|
||||||
# the probability of it being < 20 words is about 2^(-(256+12-19*11)) = 2^(-59)
|
# the probability of it being < 20 words is about 2^(-(256+12-19*11)) = 2^(-59)
|
||||||
if passphrase != '':
|
if passphrase != '':
|
||||||
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]), t, '', "m/")
|
||||||
xprv2, xpub2 = self.get_xkeys(' '.join(words[12:]), '', "m/")
|
xprv2, xpub2 = self.get_xkeys(' '.join(words[12:]), t, '', "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, t, passphrase, "m/0'/")
|
||||||
xprv2, xpub2 = self.get_xkeys(seed, passphrase, "m/1'/")
|
xprv2, xpub2 = self.get_xkeys(seed, t, passphrase, "m/1'/")
|
||||||
else:
|
else:
|
||||||
raise Exception('unrecognized seed length: {} words'.format(n))
|
raise Exception('unrecognized seed length: {} words'.format(n))
|
||||||
return xprv1, xpub1, xprv2, xpub2
|
return xprv1, xpub1, xprv2, xpub2
|
||||||
|
@ -561,7 +605,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 +623,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)
|
||||||
|
|
|
@ -572,9 +572,7 @@ class WalletStorage(JsonDB):
|
||||||
# delete verified_tx3 as its structure changed
|
# delete verified_tx3 as its structure changed
|
||||||
if not self._is_upgrade_method_needed(17, 17):
|
if not self._is_upgrade_method_needed(17, 17):
|
||||||
return
|
return
|
||||||
|
|
||||||
self.put('verified_tx3', None)
|
self.put('verified_tx3', None)
|
||||||
|
|
||||||
self.put('seed_version', 18)
|
self.put('seed_version', 18)
|
||||||
|
|
||||||
def convert_imported(self):
|
def convert_imported(self):
|
||||||
|
|
|
@ -4,7 +4,7 @@ import tempfile
|
||||||
from typing import Sequence
|
from typing import Sequence
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from electrum import storage, bitcoin, keystore
|
from electrum import storage, bitcoin, keystore, bip32
|
||||||
from electrum import Transaction
|
from electrum import Transaction
|
||||||
from electrum import SimpleConfig
|
from electrum import SimpleConfig
|
||||||
from electrum.address_synchronizer import TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT
|
from electrum.address_synchronizer import TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT
|
||||||
|
@ -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 = bip32.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))
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue