lnutil: rename LNFC.INVOICE -> BOLT11_INVOICE, add b12 LNFC
What changed, and why it matters
This commit is a straightforward code cleanup and preparation for future BOLT12 (a newer Lightning Network invoice standard) support. It renames the existing 'INVOICE' feature context to 'BOLT11_INVOICE' and adds new context labels for BOLT12-related features. There is no security fix or vulnerability present in the diff.
No security action required. Treat as normal refactoring/feature-prep commit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change refactors the LnFeatureContexts enum in electrum/lnutil.py: INVOICE becomes BOLT11_INVOICE, and four new contexts are added (BOLT12_OFFER, BOLT12_INVREQ, BOLT12_INVOICE, BLINDED_PATH). Existing feature bit context mappings are updated to use BOLT11_INVOICE, and some features (BASIC_MPP, trampoline routing variants) are additionally associated with BOLT12_INVOICE. The helper method for_invoice() is renamed to for_bolt11_invoice(), and all call sites in bolt11.py, lnworker.py, and tests are updated accordingly. No logic changes affecting security were introduced.
Changed components
electrum/lnutil.pyelectrum/bolt11.pyelectrum/lnworker.pytests/test_lnpeer.pytests/test_lnutil.pyInspect captured patch +28 / −31
diff --git a/electrum/bolt11.py b/electrum/bolt11.py
index a532c44..f020463 100644
--- a/electrum/bolt11.py
+++ b/electrum/bolt11.py
@@ -339,7 +339,7 @@ class BOLT11Addr:
from .lnutil import validate_features, ln_compare_features
invoice_features = self.get_features()
validate_features(invoice_features)
- ln_compare_features(myfeatures.for_invoice(), invoice_features)
+ ln_compare_features(myfeatures.for_bolt11_invoice(), invoice_features)
def __str__(self):
return "BOLT11Addr[{}, amount={}{} tags=[{}]]".format(
diff --git a/electrum/lnutil.py b/electrum/lnutil.py
index e30a189..9134fed 100644
--- a/electrum/lnutil.py
+++ b/electrum/lnutil.py
@@ -1409,7 +1409,11 @@ class LnFeatureContexts(enum.Flag):
CHAN_ANN_AS_IS = enum.auto()
CHAN_ANN_ALWAYS_ODD = enum.auto()
CHAN_ANN_ALWAYS_EVEN = enum.auto()
- INVOICE = enum.auto()
+ BOLT11_INVOICE = enum.auto()
+ BOLT12_OFFER = enum.auto()
+ BOLT12_INVREQ = enum.auto()
+ BOLT12_INVOICE = enum.auto()
+ BLINDED_PATH = enum.auto()
LNFC = LnFeatureContexts
@@ -1439,8 +1443,8 @@ class LnFeatures(IntFlag):
VAR_ONION_REQ = 1 << 8
VAR_ONION_OPT = 1 << 9
- _ln_feature_contexts[VAR_ONION_OPT] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
- _ln_feature_contexts[VAR_ONION_REQ] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
+ _ln_feature_contexts[VAR_ONION_OPT] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.BOLT11_INVOICE)
+ _ln_feature_contexts[VAR_ONION_REQ] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.BOLT11_INVOICE)
GOSSIP_QUERIES_EX_REQ = 1 << 10
GOSSIP_QUERIES_EX_OPT = 1 << 11
@@ -1456,14 +1460,14 @@ class LnFeatures(IntFlag):
PAYMENT_SECRET_REQ = 1 << 14
PAYMENT_SECRET_OPT = 1 << 15
_ln_feature_direct_dependencies[PAYMENT_SECRET_OPT] = {VAR_ONION_OPT}
- _ln_feature_contexts[PAYMENT_SECRET_OPT] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
- _ln_feature_contexts[PAYMENT_SECRET_REQ] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
+ _ln_feature_contexts[PAYMENT_SECRET_OPT] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.BOLT11_INVOICE)
+ _ln_feature_contexts[PAYMENT_SECRET_REQ] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.BOLT11_INVOICE)
BASIC_MPP_REQ = 1 << 16
BASIC_MPP_OPT = 1 << 17
_ln_feature_direct_dependencies[BASIC_MPP_OPT] = {PAYMENT_SECRET_OPT}
- _ln_feature_contexts[BASIC_MPP_OPT] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
- _ln_feature_contexts[BASIC_MPP_REQ] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
+ _ln_feature_contexts[BASIC_MPP_OPT] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.BOLT11_INVOICE | LNFC.BOLT12_INVOICE)
+ _ln_feature_contexts[BASIC_MPP_REQ] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.BOLT11_INVOICE | LNFC.BOLT12_INVOICE)
OPTION_SUPPORT_LARGE_CHANNEL_REQ = 1 << 18
OPTION_SUPPORT_LARGE_CHANNEL_OPT = 1 << 19
@@ -1479,44 +1483,37 @@ class LnFeatures(IntFlag):
# Temporary number.
OPTION_TRAMPOLINE_ROUTING_REQ_ECLAIR = 1 << 148
OPTION_TRAMPOLINE_ROUTING_OPT_ECLAIR = 1 << 149
-
- _ln_feature_contexts[OPTION_TRAMPOLINE_ROUTING_REQ_ECLAIR] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
- _ln_feature_contexts[OPTION_TRAMPOLINE_ROUTING_OPT_ECLAIR] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
+ _ln_feature_contexts[OPTION_TRAMPOLINE_ROUTING_REQ_ECLAIR] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.BOLT11_INVOICE | LNFC.BOLT12_INVOICE)
+ _ln_feature_contexts[OPTION_TRAMPOLINE_ROUTING_OPT_ECLAIR] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.BOLT11_INVOICE | LNFC.BOLT12_INVOICE)
# We use a different bit because Phoenix cannot do end-to-end multi-trampoline routes
OPTION_TRAMPOLINE_ROUTING_REQ_ELECTRUM = 1 << 150
OPTION_TRAMPOLINE_ROUTING_OPT_ELECTRUM = 1 << 151
-
- _ln_feature_contexts[OPTION_TRAMPOLINE_ROUTING_REQ_ELECTRUM] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
- _ln_feature_contexts[OPTION_TRAMPOLINE_ROUTING_OPT_ELECTRUM] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
+ _ln_feature_contexts[OPTION_TRAMPOLINE_ROUTING_REQ_ELECTRUM] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.BOLT11_INVOICE | LNFC.BOLT12_INVOICE)
+ _ln_feature_contexts[OPTION_TRAMPOLINE_ROUTING_OPT_ELECTRUM] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.BOLT11_INVOICE | LNFC.BOLT12_INVOICE)
OPTION_SHUTDOWN_ANYSEGWIT_REQ = 1 << 26
OPTION_SHUTDOWN_ANYSEGWIT_OPT = 1 << 27
-
_ln_feature_contexts[OPTION_SHUTDOWN_ANYSEGWIT_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
_ln_feature_contexts[OPTION_SHUTDOWN_ANYSEGWIT_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
OPTION_ONION_MESSAGE_REQ = 1 << 38
OPTION_ONION_MESSAGE_OPT = 1 << 39
-
_ln_feature_contexts[OPTION_ONION_MESSAGE_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
_ln_feature_contexts[OPTION_ONION_MESSAGE_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
OPTION_CHANNEL_TYPE_REQ = 1 << 44
OPTION_CHANNEL_TYPE_OPT = 1 << 45
-
_ln_feature_contexts[OPTION_CHANNEL_TYPE_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
_ln_feature_contexts[OPTION_CHANNEL_TYPE_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
OPTION_SCID_ALIAS_REQ = 1 << 46
OPTION_SCID_ALIAS_OPT = 1 << 47
-
_ln_feature_contexts[OPTION_SCID_ALIAS_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
_ln_feature_contexts[OPTION_SCID_ALIAS_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
OPTION_ZEROCONF_REQ = 1 << 50
OPTION_ZEROCONF_OPT = 1 << 51
-
_ln_feature_direct_dependencies[OPTION_ZEROCONF_OPT] = {OPTION_SCID_ALIAS_OPT}
_ln_feature_contexts[OPTION_ZEROCONF_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
_ln_feature_contexts[OPTION_ZEROCONF_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
@@ -1551,10 +1548,10 @@ class LnFeatures(IntFlag):
features |= (1 << flag)
return features
- def for_invoice(self) -> 'LnFeatures':
+ def for_bolt11_invoice(self) -> 'LnFeatures':
features = LnFeatures(0)
for flag in list_enabled_ln_feature_bits(self):
- if LnFeatureContexts.INVOICE & _ln_feature_contexts[1 << flag]:
+ if LnFeatureContexts.BOLT11_INVOICE & _ln_feature_contexts[1 << flag]:
features |= (1 << flag)
return features
diff --git a/electrum/lnworker.py b/electrum/lnworker.py
index 5152600..3ed9e37 100644
--- a/electrum/lnworker.py
+++ b/electrum/lnworker.py
@@ -2631,7 +2631,7 @@ class LNWallet(Logger):
return route
def _get_invoice_features(self, amount_msat: Optional[int]) -> LnFeatures:
- invoice_features = self.features.for_invoice()
+ invoice_features = self.features.for_bolt11_invoice()
if not all((not c.is_open() or c.is_frozen_for_receiving()) or self.is_trampoline_peer(c.node_id) \
for c in self.channels.values()):
invoice_features &= ~ LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT_ELECTRUM
diff --git a/tests/test_lnpeer.py b/tests/test_lnpeer.py
index b36f8b7..9d3dacf 100644
--- a/tests/test_lnpeer.py
+++ b/tests/test_lnpeer.py
@@ -431,7 +431,7 @@ class TestPeer(ElectrumTestCase):
routing_hints = []
trampoline_hints = []
if invoice_features is None:
- invoice_features = w2.features.for_invoice()
+ invoice_features = w2.features.for_bolt11_invoice()
if invoice_features.supports(LnFeatures.PAYMENT_SECRET_OPT):
payment_secret = w2.get_payment_secret(payment_hash)
else:
@@ -1057,7 +1057,7 @@ class TestPeerDirect(TestPeer):
w1, w2 = graph.workers.values()
async def try_paying_some_invoices():
# feature bits: unknown even fbit
- invoice_features = w2.features.for_invoice() | (1 << 990) # add undefined even fbit
+ invoice_features = w2.features.for_bolt11_invoice() | (1 << 990) # add undefined even fbit
lnaddr, pay_req = self.prepare_invoice(w2, invoice_features=invoice_features)
with self.assertRaises(lnutil.UnknownEvenFeatureBits):
result, log = await w1.pay_invoice(pay_req)
diff --git a/tests/test_lnutil.py b/tests/test_lnutil.py
index 1bcccd1..23ecb73 100644
--- a/tests/test_lnutil.py
+++ b/tests/test_lnutil.py
@@ -951,21 +951,21 @@ class TestLNUtil(ElectrumTestCase):
def test_ln_features_for_invoice(self):
features = LnFeatures.OPTION_DATA_LOSS_PROTECT_REQ
- self.assertEqual(LnFeatures(0), features.for_invoice())
+ self.assertEqual(LnFeatures(0), features.for_bolt11_invoice())
features = LnFeatures.PAYMENT_SECRET_OPT
- self.assertEqual(features, features.for_invoice())
+ self.assertEqual(features, features.for_bolt11_invoice())
features = LnFeatures.PAYMENT_SECRET_REQ
- self.assertEqual(features, features.for_invoice())
+ self.assertEqual(features, features.for_bolt11_invoice())
features = LnFeatures.PAYMENT_SECRET_REQ | LnFeatures.VAR_ONION_REQ
- self.assertEqual(features, features.for_invoice())
+ self.assertEqual(features, features.for_bolt11_invoice())
features = LnFeatures.BASIC_MPP_OPT | LnFeatures.PAYMENT_SECRET_REQ | LnFeatures.OPTION_DATA_LOSS_PROTECT_REQ
self.assertEqual(LnFeatures.BASIC_MPP_OPT | LnFeatures.PAYMENT_SECRET_REQ,
- features.for_invoice())
+ features.for_bolt11_invoice())
features = LnFeatures.BASIC_MPP_OPT | LnFeatures.PAYMENT_SECRET_REQ | LnFeatures.VAR_ONION_OPT | LnFeatures.OPTION_DATA_LOSS_PROTECT_REQ
self.assertEqual(LnFeatures.BASIC_MPP_OPT | LnFeatures.PAYMENT_SECRET_REQ | LnFeatures.VAR_ONION_OPT,
- features.for_invoice())
+ features.for_bolt11_invoice())
features = LnFeatures.BASIC_MPP_OPT | LnFeatures.PAYMENT_SECRET_REQ | LnFeatures.VAR_ONION_REQ
- self.assertEqual(features, features.for_invoice())
+ self.assertEqual(features, features.for_bolt11_invoice())
def test_ln_compare_features(self):
f1 = LnFeatures.OPTION_DATA_LOSS_PROTECT_REQ | LnFeatures.OPTION_DATA_LOSS_PROTECT_OPT
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.