From 22f5ff0d0ed0991d0566200513b267654ff0deae Mon Sep 17 00:00:00 2001 From: bitromortac Date: Wed, 22 Sep 2021 11:12:38 +0200 Subject: [PATCH] add static payment key * in order to be able to sweep to_remote in an onchain backup scenario we need to retain the private key for the payment_basepoint * to facilitate the above, we open a channel derived from a static secret (tied to the wallet seed), the static_payment_key combined with the funding pubkey (multisig_key), which we can restore from the channel closing transaction --- electrum/lnchannel.py | 9 ++++++++- electrum/lnpeer.py | 16 +++++++++++----- electrum/lnutil.py | 25 +++++++++++++++++++++++-- electrum/lnworker.py | 1 + 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py index 91104a204..2f92050e1 100644 --- a/electrum/lnchannel.py +++ b/electrum/lnchannel.py @@ -399,7 +399,6 @@ class AbstractChannel(Logger, ABC): # auto-remove redeemed backups self.lnworker.remove_channel_backup(self.channel_id) - @abstractmethod def is_initiator(self) -> bool: pass @@ -538,8 +537,13 @@ class ChannelBackup(AbstractChannel): self.config[LOCAL] = LocalConfig.from_seed( channel_seed=cb.channel_seed, to_self_delay=cb.local_delay, + # there are three cases of backups: + # 1. legacy: payment_basepoint will be derived + # 2. static_remotekey: to_remote sweep not necessary due to wallet address + # 3. anchor outputs: sweep to_remote by deriving the key from the funding pubkeys static_remotekey=local_payment_pubkey, # dummy values + static_payment_key=None, dust_limit_sat=None, max_htlc_value_in_flight_msat=None, max_accepted_htlcs=None, @@ -635,6 +639,9 @@ class ChannelBackup(AbstractChannel): def get_sweep_address(self) -> str: return self.lnworker.wallet.get_new_sweep_address_for_channel() + def has_anchors(self) -> Optional[bool]: + return None + def get_local_pubkey(self) -> bytes: cb = self.cb assert isinstance(cb, ChannelBackupStorage) diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py index b9b2a6eb1..359b4afc7 100644 --- a/electrum/lnpeer.py +++ b/electrum/lnpeer.py @@ -681,11 +681,16 @@ class Peer(Logger, EventListener): # flexibility to decide an address at closing time upfront_shutdown_script = b'' - assert channel_type & channel_type.OPTION_STATIC_REMOTEKEY - wallet = self.lnworker.wallet - assert wallet.txin_type == 'p2wpkh' - addr = wallet.get_new_sweep_address_for_channel() - static_remotekey = bytes.fromhex(wallet.get_public_key(addr)) + if self.use_anchors(): + static_payment_key = self.lnworker.static_payment_key + static_remotekey = None + else: + assert channel_type & channel_type.OPTION_STATIC_REMOTEKEY + wallet = self.lnworker.wallet + assert wallet.txin_type == 'p2wpkh' + addr = wallet.get_new_sweep_address_for_channel() + static_payment_key = None + static_remotekey = bytes.fromhex(wallet.get_public_key(addr)) dust_limit_sat = bitcoin.DUST_LIMIT_P2PKH reserve_sat = max(funding_sat // 100, dust_limit_sat) @@ -696,6 +701,7 @@ class Peer(Logger, EventListener): local_config = LocalConfig.from_seed( channel_seed=channel_seed, static_remotekey=static_remotekey, + static_payment_key=static_payment_key, upfront_shutdown_script=upfront_shutdown_script, to_self_delay=self.network.config.LIGHTNING_TO_SELF_DELAY_CSV, dust_limit_sat=dust_limit_sat, diff --git a/electrum/lnutil.py b/electrum/lnutil.py index a0080ab79..012f4e650 100644 --- a/electrum/lnutil.py +++ b/electrum/lnutil.py @@ -212,7 +212,6 @@ class LocalConfig(ChannelConfig): @classmethod def from_seed(cls, **kwargs): channel_seed = kwargs['channel_seed'] - static_remotekey = kwargs.pop('static_remotekey') node = BIP32Node.from_rootseed(channel_seed, xtype='standard') keypair_generator = lambda family: generate_keypair(node, family) kwargs['per_commitment_secret_seed'] = keypair_generator(LnKeyFamily.REVOCATION_ROOT).privkey @@ -220,11 +219,23 @@ class LocalConfig(ChannelConfig): kwargs['htlc_basepoint'] = keypair_generator(LnKeyFamily.HTLC_BASE) kwargs['delayed_basepoint'] = keypair_generator(LnKeyFamily.DELAY_BASE) kwargs['revocation_basepoint'] = keypair_generator(LnKeyFamily.REVOCATION_BASE) - if static_remotekey: + static_remotekey = kwargs.pop('static_remotekey') + static_payment_key = kwargs.pop('static_payment_key') + if static_payment_key: + # We derive the payment_basepoint from a static secret (derived from + # the wallet seed) and a public nonce that is revealed + # when the funding transaction is spent. This way we can restore the + # payment_basepoint, needed for sweeping in the event of a force close. + kwargs['payment_basepoint'] = derive_payment_basepoint( + static_payment_secret=static_payment_key.privkey, + funding_pubkey=kwargs['multisig_key'].pubkey + ) + elif static_remotekey: # we automatically sweep to a wallet address kwargs['payment_basepoint'] = OnlyPubkeyKeypair(static_remotekey) else: # we expect all our channels to use option_static_remotekey, so ending up here likely indicates an issue... kwargs['payment_basepoint'] = keypair_generator(LnKeyFamily.PAYMENT_BASE) + return LocalConfig(**kwargs) def validate_params(self, *, funding_sat: int, config: 'SimpleConfig', peer_features: 'LnFeatures') -> None: @@ -586,6 +597,16 @@ def derive_blinded_privkey(basepoint_secret: bytes, per_commitment_secret: bytes return int.to_bytes(sum, length=32, byteorder='big', signed=False) +def derive_payment_basepoint(static_payment_secret: bytes, funding_pubkey: bytes) -> Keypair: + assert isinstance(static_payment_secret, bytes) + assert isinstance(funding_pubkey, bytes) + payment_basepoint = ecc.ECPrivkey(sha256(static_payment_secret + funding_pubkey)) + return Keypair( + pubkey=payment_basepoint.get_public_key_bytes(), + privkey=payment_basepoint.get_secret_bytes() + ) + + def make_htlc_tx_output( amount_msat, local_feerate, revocationpubkey, local_delayedpubkey, success, to_self_delay, ) -> Tuple[bytes, PartialTxOutput]: diff --git a/electrum/lnworker.py b/electrum/lnworker.py index 5717b1b7b..65c6dff46 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -829,6 +829,7 @@ class LNWallet(LNWorker): self.db = wallet.db self.node_keypair = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.NODE_KEY) self.backup_key = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.BACKUP_CIPHER).privkey + self.static_payment_key = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.PAYMENT_BASE) self.payment_secret_key = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.PAYMENT_SECRET_KEY).privkey Logger.__init__(self) features = LNWALLET_FEATURES