tsupport option_static_remotekey - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit 2255b0715704bf9e32a4e5b9d1fda511be7b5938 DIR parent 47d14c579b0e49cd1c29c416a6b3f69f5bd9fd9e HTML Author: ThomasV <thomasv@electrum.org> Date: Fri, 13 Dec 2019 14:07:11 +0100 support option_static_remotekey Diffstat: M electrum/lnchannel.py | 9 ++++++++- M electrum/lnpeer.py | 24 +++++++++++++++++++++--- M electrum/lnsweep.py | 41 +++++++++++++++++-------------- M electrum/lnutil.py | 2 ++ M electrum/lnworker.py | 1 + 5 files changed, 54 insertions(+), 23 deletions(-) --- DIR diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py t@@ -230,6 +230,9 @@ class Channel(Logger): self._chan_ann_without_sigs = chan_ann return chan_ann + def is_static_remotekey_enabled(self): + return self.storage.get('static_remotekey_enabled') + def set_short_channel_id(self, short_id): self.short_channel_id = short_id self.storage["short_channel_id"] = short_id t@@ -766,7 +769,11 @@ class Channel(Logger): feerate, self.constraints.is_initiator == (subject == LOCAL), ) - payment_pubkey = derive_pubkey(other_config.payment_basepoint.pubkey, this_point) + if self.is_static_remotekey_enabled(): + payment_pubkey = other_config.payment_basepoint.pubkey + else: + payment_pubkey = derive_pubkey(other_config.payment_basepoint.pubkey, this_point) + return make_commitment( ctn, this_config.multisig_key.pubkey, DIR diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py t@@ -119,7 +119,7 @@ class Peer(Logger): async def initialize(self): if isinstance(self.transport, LNTransport): await self.transport.handshake() - self.send_message("init", gflen=0, lflen=1, localfeatures=self.localfeatures) + self.send_message("init", gflen=0, lflen=2, localfeatures=self.localfeatures) self._sent_init = True @property t@@ -461,6 +461,9 @@ class Peer(Logger): pass self.lnworker.peer_closed(self) + def is_static_remotekey(self): + return bool(self.localfeatures & LnLocalFeatures.OPTION_STATIC_REMOTEKEY_OPT) + def make_local_config(self, funding_sat: int, push_msat: int, initiator: HTLCOwner) -> LocalConfig: # key derivation channel_counter = self.lnworker.get_and_inc_counter_for_channel_keys() t@@ -469,8 +472,16 @@ class Peer(Logger): initial_msat = funding_sat * 1000 - push_msat else: initial_msat = push_msat + + if self.is_static_remotekey(): + addr = self.lnworker.wallet.get_unused_address() + static_key = self.lnworker.wallet.get_public_key(addr) # just a pubkey + payment_basepoint = OnlyPubkeyKeypair(bfh(static_key)) + else: + payment_basepoint = keypair_generator(LnKeyFamily.PAYMENT_BASE) + local_config=LocalConfig( - payment_basepoint=keypair_generator(LnKeyFamily.PAYMENT_BASE), + payment_basepoint=payment_basepoint, multisig_key=keypair_generator(LnKeyFamily.MULTISIG), htlc_basepoint=keypair_generator(LnKeyFamily.HTLC_BASE), delayed_basepoint=keypair_generator(LnKeyFamily.DELAY_BASE), t@@ -615,6 +626,7 @@ class Peer(Logger): 'data_loss_protect_remote_pcp': {}, "log": {}, "revocation_store": {}, + "static_remotekey_enabled": self.is_static_remotekey(), # stored because it cannot be "downgraded", per BOLT2 } channel_id = chan_dict.get('channel_id') channels = self.lnworker.db.get_dict('channels') t@@ -729,12 +741,16 @@ class Peer(Logger): next_remote_ctn = chan.get_next_ctn(REMOTE) assert self.localfeatures & LnLocalFeatures.OPTION_DATA_LOSS_PROTECT_OPT # send message + srk_enabled = chan.is_static_remotekey_enabled() + if srk_enabled: + latest_secret, latest_point = chan.get_secret_and_point(LOCAL, 0) + else: + latest_secret, latest_point = chan.get_secret_and_point(LOCAL, latest_local_ctn) if oldest_unrevoked_remote_ctn == 0: last_rev_secret = 0 else: last_rev_index = oldest_unrevoked_remote_ctn - 1 last_rev_secret = chan.revocation_store.retrieve_secret(RevocationStore.START_INDEX - last_rev_index) - latest_secret, latest_point = chan.get_secret_and_point(LOCAL, latest_local_ctn) self.send_message( "channel_reestablish", channel_id=chan_id, t@@ -824,6 +840,8 @@ class Peer(Logger): if our_pcs != their_claim_of_our_last_per_commitment_secret: self.logger.error(f"channel_reestablish: (DLP) local PCS mismatch: {bh2u(our_pcs)} != {bh2u(their_claim_of_our_last_per_commitment_secret)}") return False + if chan.is_static_remotekey_enabled(): + return True try: __, our_remote_pcp = chan.get_secret_and_point(REMOTE, their_next_local_ctn - 1) except RemoteCtnTooFarInFuture: DIR diff --git a/electrum/lnsweep.py b/electrum/lnsweep.py t@@ -324,7 +324,9 @@ def create_sweeptxs_for_their_ctx(*, chan: 'Channel', ctx: Transaction, witness_script = bh2u(make_commitment_output_to_local_witness_script( our_revocation_pubkey, our_conf.to_self_delay, their_delayed_pubkey)) to_local_address = redeem_script_to_address('p2wsh', witness_script) - our_payment_pubkey = derive_pubkey(our_conf.payment_basepoint.pubkey, their_pcp) + # to remote address + bpk = our_conf.payment_basepoint.pubkey + our_payment_pubkey = bpk if chan.is_static_remotekey_enabled() else derive_pubkey(bpk, their_pcp) to_remote_address = make_commitment_output_to_remote_address(our_payment_pubkey) # test if this is their ctx _logger.debug(f'testing their ctx: {to_local_address} {to_remote_address}') t@@ -345,26 +347,27 @@ def create_sweeptxs_for_their_ctx(*, chan: 'Channel', ctx: Transaction, our_htlc_privkey = derive_privkey(secret=int.from_bytes(our_conf.htlc_basepoint.privkey, 'big'), per_commitment_point=their_pcp) our_htlc_privkey = ecc.ECPrivkey.from_secret_scalar(our_htlc_privkey) their_htlc_pubkey = derive_pubkey(their_conf.htlc_basepoint.pubkey, their_pcp) - our_payment_bp_privkey = ecc.ECPrivkey(our_conf.payment_basepoint.privkey) - our_payment_privkey = derive_privkey(our_payment_bp_privkey.secret_scalar, their_pcp) - our_payment_privkey = ecc.ECPrivkey.from_secret_scalar(our_payment_privkey) - assert our_payment_pubkey == our_payment_privkey.get_public_key_bytes(compressed=True) # to_local is handled by lnwatcher # to_remote - output_idxs = ctx.get_output_idxs_from_address(to_remote_address) - if output_idxs: - output_idx = output_idxs.pop() - prevout = ctx.txid() + ':%d'%output_idx - sweep_tx = lambda: create_sweeptx_their_ctx_to_remote( - sweep_address=sweep_address, - ctx=ctx, - output_idx=output_idx, - our_payment_privkey=our_payment_privkey, - config=chan.lnworker.config) - txs[prevout] = SweepInfo(name='their_ctx_to_remote', - csv_delay=0, - cltv_expiry=0, - gen_tx=sweep_tx) + if not chan.is_static_remotekey_enabled(): + our_payment_bp_privkey = ecc.ECPrivkey(our_conf.payment_basepoint.privkey) + our_payment_privkey = derive_privkey(our_payment_bp_privkey.secret_scalar, their_pcp) + our_payment_privkey = ecc.ECPrivkey.from_secret_scalar(our_payment_privkey) + assert our_payment_pubkey == our_payment_privkey.get_public_key_bytes(compressed=True) + output_idxs = ctx.get_output_idxs_from_address(to_remote_address) + if output_idxs: + output_idx = output_idxs.pop() + prevout = ctx.txid() + ':%d'%output_idx + sweep_tx = lambda: create_sweeptx_their_ctx_to_remote( + sweep_address=sweep_address, + ctx=ctx, + output_idx=output_idx, + our_payment_privkey=our_payment_privkey, + config=chan.lnworker.config) + txs[prevout] = SweepInfo(name='their_ctx_to_remote', + csv_delay=0, + cltv_expiry=0, + gen_tx=sweep_tx) # HTLCs def create_sweeptx_for_htlc(htlc: 'UpdateAddHtlc', is_received_htlc: bool, ctx_output_idx: int) -> None: DIR diff --git a/electrum/lnutil.py b/electrum/lnutil.py t@@ -630,6 +630,8 @@ class LnLocalFeatures(IntFlag): OPTION_UPFRONT_SHUTDOWN_SCRIPT_OPT = 1 << 5 GOSSIP_QUERIES_REQ = 1 << 6 GOSSIP_QUERIES_OPT = 1 << 7 + OPTION_STATIC_REMOTEKEY_REQ = 1 << 12 + OPTION_STATIC_REMOTEKEY_OPT = 1 << 13 # note that these are powers of two, not the bits themselves LN_LOCAL_FEATURES_KNOWN_SET = set(LnLocalFeatures) DIR diff --git a/electrum/lnworker.py b/electrum/lnworker.py t@@ -130,6 +130,7 @@ class LNWorker(Logger): # note that e.g. DATA_LOSS_PROTECT is needed for LNGossip as many peers require it self.localfeatures = LnLocalFeatures(0) self.localfeatures |= LnLocalFeatures.OPTION_DATA_LOSS_PROTECT_OPT + self.localfeatures |= LnLocalFeatures.OPTION_STATIC_REMOTEKEY_OPT def channels_for_peer(self, node_id): return {}