Browse Source

Merge pull request #7153 from SomberNight/202103_ks_seed_type

keystore to store seed_type; wallet.init_lightning() to sometimes create deterministic LN keys
patch-4
ThomasV 4 years ago
committed by GitHub
parent
commit
21b628d7a1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      electrum/base_wizard.py
  2. 19
      electrum/gui/kivy/main_window.py
  3. 24
      electrum/gui/qt/main_window.py
  4. 21
      electrum/keystore.py
  5. 36
      electrum/wallet.py
  6. 26
      electrum/wallet_db.py

2
electrum/base_wizard.py

@ -545,7 +545,7 @@ class BaseWizard(Logger):
def create_keystore(self, seed, passphrase):
k = keystore.from_seed(seed, passphrase, self.wallet_type == 'multisig')
if self.wallet_type == 'standard' and self.seed_type == 'segwit':
if k.can_have_deterministic_lightning_xprv():
self.data['lightning_xprv'] = k.get_lightning_xprv(None)
self.on_keystore(k)

19
electrum/gui/kivy/main_window.py

@ -1421,23 +1421,26 @@ class ElectrumWindow(App, Logger):
"This means that you must save a backup of your wallet everytime you create a new channel.\n\n"
"If you want to have recoverable channels, you must create a new wallet with an Electrum seed")
self.show_info(msg)
else:
if self.wallet.can_have_lightning():
root.dismiss()
elif self.wallet.can_have_lightning():
root.dismiss()
if self.wallet.can_have_deterministic_lightning():
msg = messages.MSG_LIGHTNING_SCB_WARNING + "\n" + _("Create lightning keys?")
else:
msg = _(
"Warning: this wallet type does not support channel recovery from seed. "
"You will need to backup your wallet everytime you create a new wallet. "
"Create lightning keys?")
d = Question(msg, self._enable_lightning, title=_('Enable Lightning?'))
d.open()
else:
pass
d = Question(msg, self._enable_lightning, title=_('Enable Lightning?'))
d.open()
def _enable_lightning(self, b):
if not b:
return
self.protected(_("Create lightning keys?"), self.__enable_lightning, ())
def __enable_lightning(self, password):
wallet_path = self.get_wallet_path()
self.wallet.init_lightning()
self.wallet.init_lightning(password=password)
self.show_info(_('Lightning keys have been initialized.'))
self.stop_wallet()
self.load_wallet_by_name(wallet_path)

24
electrum/gui/qt/main_window.py

@ -2386,12 +2386,21 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
self.set_contact(line2.text(), line1.text())
def init_lightning_dialog(self):
if self.question(_(
assert not self.wallet.has_lightning()
if self.wallet.can_have_deterministic_lightning():
msg = messages.MSG_LIGHTNING_SCB_WARNING + "\n" + _("Create lightning keys?")
else:
msg = _(
"Warning: this wallet type does not support channel recovery from seed. "
"You will need to backup your wallet everytime you create a new wallet. "
"Create lightning keys?")):
self.wallet.init_lightning()
self.show_message("Lightning keys created. Please restart Electrum")
"Create lightning keys?")
if self.question(msg):
self._init_lightning_dialog()
@protected
def _init_lightning_dialog(self, *, password):
self.wallet.init_lightning(password=password)
self.show_message("Lightning keys created. Please restart Electrum")
def show_wallet_info(self):
dialog = WindowModalDialog(self, _("Wallet Information"))
@ -2400,7 +2409,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
wallet_type = self.wallet.db.get('wallet_type', '')
if self.wallet.is_watching_only():
wallet_type += ' [{}]'.format(_('watching-only'))
seed_available = _('True') if self.wallet.has_seed() else _('False')
seed_available = _('False')
if self.wallet.has_seed():
seed_available = _('True')
ks = self.wallet.keystore
assert isinstance(ks, keystore.Deterministic_KeyStore)
seed_available += f" ({ks.get_seed_type()})"
keystore_types = [k.get_type_text() for k in self.wallet.get_keystores()]
grid = QGridLayout()
basename = os.path.basename(self.wallet.storage.path)

21
electrum/keystore.py

@ -160,6 +160,9 @@ class KeyStore(Logger, ABC):
return pubkey, list(path)
return None, None
def can_have_deterministic_lightning_xprv(self) -> bool:
return False
class Software_KeyStore(KeyStore):
@ -282,8 +285,9 @@ class Deterministic_KeyStore(Software_KeyStore):
def __init__(self, d):
Software_KeyStore.__init__(self, d)
self.seed = d.get('seed', '')
self.seed = d.get('seed', '') # only electrum seeds
self.passphrase = d.get('passphrase', '')
self._seed_type = d.get('seed_type', None) # only electrum seeds
def is_deterministic(self):
return True
@ -297,11 +301,16 @@ class Deterministic_KeyStore(Software_KeyStore):
d['seed'] = self.seed
if self.passphrase:
d['passphrase'] = self.passphrase
if self._seed_type:
d['seed_type'] = self._seed_type
return d
def has_seed(self):
return bool(self.seed)
def get_seed_type(self) -> Optional[str]:
return self._seed_type
def is_watching_only(self):
return not self.has_seed()
@ -313,6 +322,7 @@ class Deterministic_KeyStore(Software_KeyStore):
if self.seed:
raise Exception("a seed exists")
self.seed = self.format_seed(seed)
self._seed_type = seed_type(seed) or None
def get_seed(self, password):
if not self.has_seed():
@ -613,7 +623,14 @@ class BIP32_KeyStore(Xpub, Deterministic_KeyStore):
cK = ecc.ECPrivkey(k).get_public_key_bytes()
return cK, k
def get_lightning_xprv(self, password):
def can_have_deterministic_lightning_xprv(self):
if (self.get_seed_type() == 'segwit'
and self.get_bip32_node_for_xpub().xtype == 'p2wpkh'):
return True
return False
def get_lightning_xprv(self, password) -> str:
assert self.can_have_deterministic_lightning_xprv()
xprv = self.get_master_private_key(password)
rootnode = BIP32Node.from_xkey(xprv)
node = rootnode.subkey_at_private_derivation("m/67'/")

36
electrum/wallet.py

@ -333,24 +333,34 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
new_db.write(new_storage)
return new_path
def has_lightning(self):
def has_lightning(self) -> bool:
return bool(self.lnworker)
def can_have_lightning(self):
def can_have_lightning(self) -> bool:
# we want static_remotekey to be a wallet address
return self.txin_type == 'p2wpkh'
def init_lightning(self):
def can_have_deterministic_lightning(self) -> bool:
if not self.can_have_lightning():
return False
if not self.keystore:
return False
return self.keystore.can_have_deterministic_lightning_xprv()
def init_lightning(self, *, password) -> None:
assert self.can_have_lightning()
assert self.db.get('lightning_xprv') is None
if self.db.get('lightning_privkey2'):
return
# TODO derive this deterministically from wallet.keystore at keystore generation time
# probably along a hardened path ( lnd-equivalent would be m/1017'/coinType'/ )
seed = os.urandom(32)
node = BIP32Node.from_rootseed(seed, xtype='standard')
ln_xprv = node.to_xprv()
self.db.put('lightning_privkey2', ln_xprv)
assert self.db.get('lightning_privkey2') is None
if self.can_have_deterministic_lightning():
ks = self.keystore
assert isinstance(ks, keystore.BIP32_KeyStore)
self.db.put('lightning_xprv', ks.get_lightning_xprv(password))
else:
seed = os.urandom(32)
node = BIP32Node.from_rootseed(seed, xtype='standard')
ln_xprv = node.to_xprv()
self.db.put('lightning_privkey2', ln_xprv)
async def stop(self):
"""Stop all networking and save DB to disk."""
@ -3185,7 +3195,7 @@ def create_new_wallet(*, path, config: SimpleConfig, passphrase=None, password=N
k = keystore.from_seed(seed, passphrase)
db.put('keystore', k.dump())
db.put('wallet_type', 'standard')
if keystore.seed_type(seed) == 'segwit':
if k.can_have_deterministic_lightning_xprv():
db.put('lightning_xprv', k.get_lightning_xprv(None))
if gap_limit is not None:
db.put('gap_limit', gap_limit)
@ -3229,7 +3239,7 @@ def restore_wallet_from_text(text, *, path, config: SimpleConfig,
k = keystore.from_master_key(text)
elif keystore.is_seed(text):
k = keystore.from_seed(text, passphrase)
if keystore.seed_type(text) == 'segwit':
if k.can_have_deterministic_lightning_xprv():
db.put('lightning_xprv', k.get_lightning_xprv(None))
else:
raise Exception("Seed or key not recognized")

26
electrum/wallet_db.py

@ -53,7 +53,7 @@ if TYPE_CHECKING:
OLD_SEED_VERSION = 4 # electrum versions < 2.0
NEW_SEED_VERSION = 11 # electrum versions >= 2.0
FINAL_SEED_VERSION = 39 # electrum >= 2.7 will set this to prevent
FINAL_SEED_VERSION = 40 # electrum >= 2.7 will set this to prevent
# old versions from overwriting new format
@ -188,6 +188,7 @@ class WalletDB(JsonDB):
self._convert_version_37()
self._convert_version_38()
self._convert_version_39()
self._convert_version_40()
self.put('seed_version', FINAL_SEED_VERSION) # just to be sure
self._after_upgrade_tasks()
@ -787,6 +788,29 @@ class WalletDB(JsonDB):
self.data['imported_channel_backups'] = self.data.pop('channel_backups', {})
self.data['seed_version'] = 39
def _convert_version_40(self):
# put 'seed_type' into keystores
if not self._is_upgrade_method_needed(39, 39):
return
for ks_name in ('keystore', *['x{}/'.format(i) for i in range(1, 16)]):
ks = self.data.get(ks_name, None)
if ks is None: continue
seed = ks.get('seed')
if not seed: continue
seed_type = None
xpub = ks.get('xpub') or None
if xpub:
assert isinstance(xpub, str)
if xpub[0:4] in ('xpub', 'tpub'):
seed_type = 'standard'
elif xpub[0:4] in ('zpub', 'Zpub', 'vpub', 'Vpub'):
seed_type = 'segwit'
elif ks.get('type') == 'old':
seed_type = 'old'
if seed_type is not None:
ks['seed_type'] = seed_type
self.data['seed_version'] = 40
def _convert_imported(self):
if not self._is_upgrade_method_needed(0, 13):
return

Loading…
Cancel
Save