From 71635216dfc36b73aab111324ffa8c1f43e4740c Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 25 Mar 2020 13:44:39 +0100 Subject: [PATCH] ln feature bits: validate transitive feature deps everywhere --- electrum/channel_db.py | 20 +++++++++++++------- electrum/lnaddr.py | 2 +- electrum/lnutil.py | 15 +++++++++++---- electrum/tests/test_bolt11.py | 10 +++++----- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/electrum/channel_db.py b/electrum/channel_db.py index 398790566..f0da99897 100644 --- a/electrum/channel_db.py +++ b/electrum/channel_db.py @@ -39,7 +39,7 @@ from . import constants from .util import bh2u, profiler, get_headers_dir, bfh, is_ip_address, list_enabled_bits from .logging import Logger from .lnutil import (LNPeerAddr, format_short_channel_id, ShortChannelID, - UnknownEvenFeatureBits, validate_features) + validate_features, IncompatibleOrInsaneFeatures) from .lnverifier import LNChannelVerifier, verify_sig_for_channel_update from .lnmsg import decode_msg @@ -331,8 +331,8 @@ class ChannelDB(SqlDB): continue try: channel_info = ChannelInfo.from_msg(msg) - except UnknownEvenFeatureBits: - self.logger.info("unknown feature bits") + except IncompatibleOrInsaneFeatures as e: + self.logger.info(f"unknown or insane feature bits: {e!r}") continue if trusted: added += 1 @@ -346,7 +346,7 @@ class ChannelDB(SqlDB): def add_verified_channel_info(self, msg: dict, *, capacity_sat: int = None) -> None: try: channel_info = ChannelInfo.from_msg(msg) - except UnknownEvenFeatureBits: + except IncompatibleOrInsaneFeatures: return channel_info = channel_info._replace(capacity_sat=capacity_sat) with self.lock: @@ -499,7 +499,7 @@ class ChannelDB(SqlDB): for msg_payload in msg_payloads: try: node_info, node_addresses = NodeInfo.from_msg(msg_payload) - except UnknownEvenFeatureBits: + except IncompatibleOrInsaneFeatures: continue node_id = node_info.node_id # Ignore node if it has no associated channel (DoS protection) @@ -593,11 +593,17 @@ class ChannelDB(SqlDB): self._recent_peers = sorted_node_ids[:self.NUM_MAX_RECENT_PEERS] c.execute("""SELECT * FROM channel_info""") for short_channel_id, msg in c: - ci = ChannelInfo.from_raw_msg(msg) + try: + ci = ChannelInfo.from_raw_msg(msg) + except IncompatibleOrInsaneFeatures: + continue self._channels[ShortChannelID.normalize(short_channel_id)] = ci c.execute("""SELECT * FROM node_info""") for node_id, msg in c: - node_info, node_addresses = NodeInfo.from_raw_msg(msg) + try: + node_info, node_addresses = NodeInfo.from_raw_msg(msg) + except IncompatibleOrInsaneFeatures: + continue # don't load node_addresses because they dont have timestamps self._nodes[node_id] = node_info c.execute("""SELECT * FROM policy""") diff --git a/electrum/lnaddr.py b/electrum/lnaddr.py index 12ca0b3c9..a10054055 100644 --- a/electrum/lnaddr.py +++ b/electrum/lnaddr.py @@ -170,7 +170,7 @@ def pull_tagged(stream): length = stream.read(5).uint * 32 + stream.read(5).uint return (CHARSET[tag], stream.read(length * 5), stream) -def lnencode(addr: 'LnAddr', privkey): +def lnencode(addr: 'LnAddr', privkey) -> str: if addr.amount: amount = Decimal(str(addr.amount)) # We can only send down to millisatoshi. diff --git a/electrum/lnutil.py b/electrum/lnutil.py index f29a08eec..4536bdeb9 100644 --- a/electrum/lnutil.py +++ b/electrum/lnutil.py @@ -165,7 +165,6 @@ class HandshakeFailed(LightningError): pass class ConnStringFormatError(LightningError): pass class UnknownPaymentHash(LightningError): pass class RemoteMisbehaving(LightningError): pass -class UnknownEvenFeatureBits(Exception): pass class NotFoundChanAnnouncementForUpdate(Exception): pass @@ -855,7 +854,11 @@ def get_ln_flag_pair_of_bit(flag_bit: int) -> int: return flag_bit - 1 -class IncompatibleLightningFeatures(ValueError): pass + +class IncompatibleOrInsaneFeatures(Exception): pass +class UnknownEvenFeatureBits(IncompatibleOrInsaneFeatures): pass +class IncompatibleLightningFeatures(IncompatibleOrInsaneFeatures): pass + def ln_compare_features(our_features: 'LnFeatures', their_features: int) -> 'LnFeatures': """Returns negotiated features. @@ -886,13 +889,17 @@ def ln_compare_features(our_features: 'LnFeatures', their_features: int) -> 'LnF def validate_features(features: int) -> None: - """Raises UnknownEvenFeatureBits if there is an unimplemented - mandatory feature. + """Raises IncompatibleOrInsaneFeatures if + - a mandatory feature is listed that we don't recognize, or + - the features are inconsistent """ + features = LnFeatures(features) enabled_features = list_enabled_bits(features) for fbit in enabled_features: if (1 << fbit) & LN_FEATURES_IMPLEMENTED == 0 and fbit % 2 == 0: raise UnknownEvenFeatureBits(fbit) + if not features.validate_transitive_dependecies(): + raise IncompatibleOrInsaneFeatures("not all transitive dependencies are set") def derive_payment_secret_from_payment_preimage(payment_preimage: bytes) -> bytes: diff --git a/electrum/tests/test_bolt11.py b/electrum/tests/test_bolt11.py index 42f70ff36..8a6bbefc2 100644 --- a/electrum/tests/test_bolt11.py +++ b/electrum/tests/test_bolt11.py @@ -78,11 +78,11 @@ class TestBolt11(ElectrumTestCase): LnAddr(paymenthash=RHASH, amount=24, tags=[('h', longdescription), ('9', 514)]), LnAddr(paymenthash=RHASH, amount=24, tags=[('h', longdescription), ('9', 10 + (1 << 8))]), LnAddr(paymenthash=RHASH, amount=24, tags=[('h', longdescription), ('9', 10 + (1 << 9))]), - LnAddr(paymenthash=RHASH, amount=24, tags=[('h', longdescription), ('9', 10 + (1 << 11))]), + LnAddr(paymenthash=RHASH, amount=24, tags=[('h', longdescription), ('9', 10 + (1 << 7) + (1 << 11))]), LnAddr(paymenthash=RHASH, amount=24, tags=[('h', longdescription), ('9', 10 + (1 << 12))]), LnAddr(paymenthash=RHASH, amount=24, tags=[('h', longdescription), ('9', 10 + (1 << 13))]), - LnAddr(paymenthash=RHASH, amount=24, tags=[('h', longdescription), ('9', 10 + (1 << 14))]), - LnAddr(paymenthash=RHASH, amount=24, tags=[('h', longdescription), ('9', 10 + (1 << 15))]), + LnAddr(paymenthash=RHASH, amount=24, tags=[('h', longdescription), ('9', 10 + (1 << 9) + (1 << 14))]), + LnAddr(paymenthash=RHASH, amount=24, tags=[('h', longdescription), ('9', 10 + (1 << 9) + (1 << 15))]), LnAddr(paymenthash=RHASH, amount=24, tags=[('h', longdescription), ('9', 33282)], payment_secret=b"\x11" * 32), ] @@ -128,8 +128,8 @@ class TestBolt11(ElectrumTestCase): lndecode("lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdees9q4pqqqqqqqqqqqqqqqqqqszk3ed62snp73037h4py4gry05eltlp0uezm2w9ajnerhmxzhzhsu40g9mgyx5v3ad4aqwkmvyftzk4k9zenz90mhjcy9hcevc7r3lx2sphzfxz7") def test_payment_secret(self): - lnaddr = lndecode("lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q5sqqqqqqqqqqqqqqqpqqq4u9s93jtgysm3mrwll70zr697y3mf902hvxwej0v7c62rsltw83ng0pu8w3j230sluc5gxkdmm9dvpy9y6ggtjd2w544mzdrcs42t7sqdkcy8h") - self.assertEqual((1 << 15) + (1 << 99) , lnaddr.get_tag('9')) + lnaddr = lndecode("lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsdq5vdhkven9v5sxyetpdees9q5sqqqqqqqqqqqqqqqpqsqvvh7ut50r00p3pg34ea68k7zfw64f8yx9jcdk35lh5ft8qdr8g4r0xzsdcrmcy9hex8un8d8yraewvhqc9l0sh8l0e0yvmtxde2z0hgpzsje5l") + self.assertEqual((1 << 9) + (1 << 15) + (1 << 99), lnaddr.get_tag('9')) self.assertEqual(b"\x11" * 32, lnaddr.payment_secret) def test_derive_payment_secret_from_payment_preimage(self):