From 306cac192b600844a64f224139404b70964bec85 Mon Sep 17 00:00:00 2001 From: f321x Date: Wed, 11 Mar 2026 09:51:11 +0100 Subject: [PATCH 1/3] lnaddr: rename LnAddr -> bolt11 The LnAddr, lndecode and lnencode naming didn't imply that it is bolt 11 specific, making it confusing to work with, now that there are also bolt 12 "lnaddr". Renaming it to *bolt11* creates a clear separation to bolt 12 things and reduces mental load. This commit is pure renaming (using the PyCharm IDE refactor function), except for the removal of the `object` inheritance of LnAddr/BOLT11Addr, this is Python 2 legacy. --- electrum/bip21.py | 6 +-- electrum/{lnaddr.py => bolt11.py} | 42 +++++++-------- electrum/gui/qt/channel_details.py | 2 +- electrum/gui/qt/main_window.py | 6 +-- electrum/gui/text.py | 4 +- electrum/invoices.py | 10 ++-- electrum/lnurl.py | 10 ++-- electrum/lnworker.py | 20 ++++---- electrum/payment_identifier.py | 4 +- electrum/plugins/watchtower/server.py | 2 +- electrum/submarine_swaps.py | 4 +- electrum/wallet_db.py | 12 ++--- tests/test_bolt11.py | 74 +++++++++++++-------------- tests/test_commands.py | 4 +- tests/test_lnpeer.py | 18 +++---- 15 files changed, 109 insertions(+), 109 deletions(-) rename electrum/{lnaddr.py => bolt11.py} (93%) diff --git a/electrum/bip21.py b/electrum/bip21.py index c7f3ecfa3..3db495e7b 100644 --- a/electrum/bip21.py +++ b/electrum/bip21.py @@ -7,7 +7,7 @@ from typing import Optional from . import bitcoin from .util import format_satoshis_plain from .bitcoin import COIN, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC -from .lnaddr import lndecode, LnDecodeException +from .bolt11 import decode_bolt11_invoice, BOLT11DecodeException # note: when checking against these, use .lower() to support case-insensitivity BITCOIN_BIP21_URI_SCHEME = 'bitcoin' @@ -93,8 +93,8 @@ def parse_bip21_URI(uri: str) -> dict: raise InvalidBitcoinURI(f"failed to parse 'sig' field: {repr(e)}") from e if 'lightning' in out: try: - lnaddr = lndecode(out['lightning']) - except LnDecodeException as e: + lnaddr = decode_bolt11_invoice(out['lightning']) + except BOLT11DecodeException as e: raise InvalidBitcoinURI(f"Failed to decode 'lightning' field: {e!r}") from e amount_sat = out.get('amount') if amount_sat: diff --git a/electrum/lnaddr.py b/electrum/bolt11.py similarity index 93% rename from electrum/lnaddr.py rename to electrum/bolt11.py index 031676840..0d910a919 100644 --- a/electrum/lnaddr.py +++ b/electrum/bolt11.py @@ -23,9 +23,9 @@ if TYPE_CHECKING: from .lnutil import LnFeatures -class LnInvoiceException(Exception): pass -class LnDecodeException(LnInvoiceException): pass -class LnEncodeException(LnInvoiceException): pass +class BOLT11InvoiceException(Exception): pass +class BOLT11DecodeException(BOLT11InvoiceException): pass +class BOLT11EncodeException(BOLT11InvoiceException): pass # BOLT #11: @@ -68,7 +68,7 @@ def unshorten_amount(amount) -> Decimal: # A reader SHOULD fail if `amount` contains a non-digit, or is followed by # anything except a `multiplier` in the table above. if not re.fullmatch("\\d+[pnum]?", str(amount)): - raise LnDecodeException("Invalid amount '{}'".format(amount)) + raise BOLT11DecodeException("Invalid amount '{}'".format(amount)) if unit in units.keys(): return Decimal(amount[:-1]) / units[unit] @@ -88,7 +88,7 @@ def encode_fallback_addr(fallback: str, net: Type[AbstractNet]) -> Sequence[int] elif addrtype == net.ADDRTYPE_P2SH: wver = 18 else: - raise LnEncodeException(f"Unknown address type {addrtype} for {net}") + raise BOLT11EncodeException(f"Unknown address type {addrtype} for {net}") wprog = addr data5 = convertbits(wprog, 8, 5) assert data5 is not None @@ -156,7 +156,7 @@ def pull_tagged(data5: bytearray) -> Tuple[str, Sequence[int]]: return ret -def lnencode(addr: 'LnAddr', privkey) -> str: +def encode_bolt11_invoice(addr: 'BOLT11Addr', privkey) -> str: if addr.amount: amount = addr.net.BOLT11_HRP + shorten_amount(addr.amount) else: @@ -185,7 +185,7 @@ def lnencode(addr: 'LnAddr', privkey) -> str: # A writer MUST NOT include more than one `d`, `h`, `n` or `x` fields, if k in ('d', 'h', 'n', 'x', 'p', 's', '9'): if k in tags_set: - raise LnEncodeException("Duplicate '{}' tag".format(k)) + raise BOLT11EncodeException("Duplicate '{}' tag".format(k)) if k == 'r': route = bytearray() @@ -228,7 +228,7 @@ def lnencode(addr: 'LnAddr', privkey) -> str: data5 += tagged5('9', feature_bits) else: # FIXME: Support unknown tags? - raise LnEncodeException("Unknown tag {}".format(k)) + raise BOLT11EncodeException("Unknown tag {}".format(k)) tags_set.add(k) @@ -254,7 +254,7 @@ def lnencode(addr: 'LnAddr', privkey) -> str: return bech32_encode(segwit_addr.Encoding.BECH32, hrp, data5) -class LnAddr(object): +class BOLT11Addr: def __init__(self, *, paymenthash: bytes = None, amount=None, net: Type[AbstractNet] = None, tags=None, date=None, payment_secret: bytes = None): self.date = int(time.time()) if not date else int(date) @@ -274,16 +274,16 @@ class LnAddr(object): @amount.setter def amount(self, value): if not (isinstance(value, Decimal) or value is None): - raise LnInvoiceException(f"amount must be Decimal or None, not {value!r}") + raise BOLT11InvoiceException(f"amount must be Decimal or None, not {value!r}") if value is None: self._amount = None return assert isinstance(value, Decimal) if value.is_nan() or not (0 <= value <= TOTAL_COIN_SUPPLY_LIMIT_IN_BTC): - raise LnInvoiceException(f"amount is out-of-bounds: {value!r} BTC") + raise BOLT11InvoiceException(f"amount is out-of-bounds: {value!r} BTC") if value * 10**12 % 10: # max resolution is millisatoshi - raise LnInvoiceException(f"Cannot encode {value!r}: too many decimal places") + raise BOLT11InvoiceException(f"Cannot encode {value!r}: too many decimal places") self._amount = value def get_amount_sat(self) -> Optional[Decimal]: @@ -341,7 +341,7 @@ class LnAddr(object): ln_compare_features(myfeatures.for_invoice(), invoice_features) def __str__(self): - return "LnAddr[{}, amount={}{} tags=[{}]]".format( + return "BOLT11Addr[{}, amount={}{} tags=[{}]]".format( hexlify(self.pubkey.serialize()).decode('utf-8') if self.pubkey else None, self.amount, self.net.BOLT11_HRP, ", ".join([k + '=' + str(v) for k, v in self.tags]) @@ -403,7 +403,7 @@ class SerializableKey: return self.pubkey.get_public_key_bytes(True) -def lndecode(invoice: str, *, verbose=False, net=None) -> LnAddr: +def decode_bolt11_invoice(invoice: str, *, verbose=False, net=None) -> BOLT11Addr: """Parses a string into an LnAddr object. Can raise LnDecodeException or IncompatibleOrInsaneFeatures. """ @@ -413,27 +413,27 @@ def lndecode(invoice: str, *, verbose=False, net=None) -> LnAddr: hrp = decoded_bech32.hrp data5 = decoded_bech32.data # "5" as in list of 5-bit integers if decoded_bech32.encoding is None: - raise LnDecodeException("Bad bech32 checksum") + raise BOLT11DecodeException("Bad bech32 checksum") if decoded_bech32.encoding != segwit_addr.Encoding.BECH32: - raise LnDecodeException("Bad bech32 encoding: must be using vanilla BECH32") + raise BOLT11DecodeException("Bad bech32 encoding: must be using vanilla BECH32") # BOLT #11: # # A reader MUST fail if it does not understand the `prefix`. if not hrp.startswith('ln'): - raise LnDecodeException("Does not start with ln") + raise BOLT11DecodeException("Does not start with ln") if not hrp[2:].startswith(net.BOLT11_HRP): - raise LnDecodeException(f"Wrong Lightning invoice HRP {hrp[2:]}, should be {net.BOLT11_HRP}") + raise BOLT11DecodeException(f"Wrong Lightning invoice HRP {hrp[2:]}, should be {net.BOLT11_HRP}") # Final signature 65 bytes, split it off. if len(data5) < 65*8//5: - raise LnDecodeException("Too short to contain signature") + raise BOLT11DecodeException("Too short to contain signature") sigdecoded = bytes(convertbits(data5[-65*8//5:], 5, 8, False)) data5 = data5[:-65*8//5] data5_remaining = bytearray(data5) # note: bytearray is faster than list of ints - addr = LnAddr() + addr = BOLT11Addr() addr.pubkey = None addr.net = net @@ -580,7 +580,7 @@ def lndecode(invoice: str, *, verbose=False, net=None) -> LnAddr: # A reader MUST use the `n` field to validate the signature instead of # performing signature recovery if a valid `n` field is provided. if not ecc.ECPubkey(addr.pubkey).ecdsa_verify(sigdecoded[:64], hrp_hash): - raise LnDecodeException("bad signature") + raise BOLT11DecodeException("bad signature") pubkey_copy = addr.pubkey class WrappedBytesKey: diff --git a/electrum/gui/qt/channel_details.py b/electrum/gui/qt/channel_details.py index 0bc16527b..3369dd10d 100644 --- a/electrum/gui/qt/channel_details.py +++ b/electrum/gui/qt/channel_details.py @@ -10,7 +10,7 @@ from electrum.i18n import _ from electrum.util import format_time from electrum.lnutil import format_short_channel_id, LOCAL, REMOTE, UpdateAddHtlc, Direction from electrum.lnchannel import htlcsum, Channel, AbstractChannel, HTLCWithStatus -from electrum.lnaddr import LnAddr, lndecode +from electrum.bolt11 import BOLT11Addr, decode_bolt11_invoice from electrum.bitcoin import COIN from electrum.wallet import Abstract_Wallet diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index d6bb67b3d..6e1c35d3e 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -73,7 +73,7 @@ from electrum.exchange_rate import FxThread from electrum.simple_config import SimpleConfig from electrum.logging import Logger from electrum.lntransport import extract_nodeid, ConnStringFormatError -from electrum.lnaddr import lndecode, LnAddr +from electrum.bolt11 import decode_bolt11_invoice, BOLT11Addr from electrum.submarine_swaps import SwapServerTransport, NostrTransport from electrum.fee_policy import FeePolicy @@ -1678,7 +1678,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): def show_lightning_invoice(self, invoice: Invoice): from electrum.util import format_short_id - lnaddr = lndecode(invoice.lightning_invoice) + lnaddr = decode_bolt11_invoice(invoice.lightning_invoice) d = WindowModalDialog(self, _("Lightning Invoice")) vbox = QVBoxLayout(d) grid = QGridLayout() @@ -1713,7 +1713,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): grid.addWidget(QLabel(_('Text') + ':'), 8, 0) grid.addWidget(invoice_e, 8, 1) r_tags = lnaddr.get_routing_info('r') - r_tags = '\n'.join(repr(r) for r in LnAddr.format_bolt11_routing_info_as_human_readable(r_tags)) + r_tags = '\n'.join(repr(r) for r in BOLT11Addr.format_bolt11_routing_info_as_human_readable(r_tags)) routing_e = QTextEdit(str(r_tags)) routing_e.setReadOnly(True) grid.addWidget(QLabel(_("Routing Hints") + ':'), 9, 0) diff --git a/electrum/gui/text.py b/electrum/gui/text.py index 09fc64c1e..1c8619310 100644 --- a/electrum/gui/text.py +++ b/electrum/gui/text.py @@ -50,9 +50,9 @@ def parse_bip21(text): def parse_bolt11(text): - from electrum.lnaddr import lndecode + from electrum.bolt11 import decode_bolt11_invoice try: - return lndecode(text) + return decode_bolt11_invoice(text) except Exception: return diff --git a/electrum/invoices.py b/electrum/invoices.py index 8f20166cb..c15f32926 100644 --- a/electrum/invoices.py +++ b/electrum/invoices.py @@ -9,7 +9,7 @@ from .i18n import _ from .util import age, InvoiceError, format_satoshis from .bip21 import create_bip21_uri from .lnutil import hex_to_bytes -from .lnaddr import lndecode, LnAddr +from .bolt11 import decode_bolt11_invoice, BOLT11Addr from . import constants from .bitcoin import COIN, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC from .bitcoin import address_to_script @@ -213,7 +213,7 @@ class BaseInvoice(StoredObject): Might raise InvoiceError. """ try: - lnaddr = lndecode(invoice) + lnaddr = decode_bolt11_invoice(invoice) except Exception as e: raise InvoiceError(e) from e amount_msat = lnaddr.get_amount_msat() @@ -275,9 +275,9 @@ class Invoice(BaseInvoice): return address @property - def _lnaddr(self) -> LnAddr: + def _lnaddr(self) -> BOLT11Addr: if self.__lnaddr is None: - self.__lnaddr = lndecode(self.lightning_invoice) + self.__lnaddr = decode_bolt11_invoice(self.lightning_invoice) return self.__lnaddr @property @@ -288,7 +288,7 @@ class Invoice(BaseInvoice): @lightning_invoice.validator def _validate_invoice_str(self, attribute, value): if value is not None: - lnaddr = lndecode(value) # this checks the str can be decoded + lnaddr = decode_bolt11_invoice(value) # this checks the str can be decoded self.__lnaddr = lnaddr # save it, just to avoid having to recompute later def can_be_paid_onchain(self) -> bool: diff --git a/electrum/lnurl.py b/electrum/lnurl.py index e08f254d6..603ff201a 100644 --- a/electrum/lnurl.py +++ b/electrum/lnurl.py @@ -12,7 +12,7 @@ import aiohttp.client_exceptions from electrum import segwit_addr, util from electrum.segwit_addr import bech32_decode, Encoding, convertbits, bech32_encode -from electrum.lnaddr import LnDecodeException, LnEncodeException +from electrum.bolt11 import BOLT11DecodeException, BOLT11EncodeException from electrum.network import Network from electrum.logging import get_logger from electrum.i18n import _ @@ -47,11 +47,11 @@ def decode_lnurl(lnurl: str) -> str: hrp = decoded_bech32.hrp data = decoded_bech32.data if decoded_bech32.encoding is None: - raise LnDecodeException("Bad bech32 checksum") + raise BOLT11DecodeException("Bad bech32 checksum") if decoded_bech32.encoding != Encoding.BECH32: - raise LnDecodeException("Bad bech32 encoding: must be using vanilla BECH32") + raise BOLT11DecodeException("Bad bech32 encoding: must be using vanilla BECH32") if not hrp.startswith("lnurl"): - raise LnDecodeException("Does not start with lnurl") + raise BOLT11DecodeException("Does not start with lnurl") data = convertbits(data, 5, 8, False) url = bytes(data).decode("utf-8") return url @@ -62,7 +62,7 @@ def encode_lnurl(url: str) -> str: try: url = url.encode("utf-8") except UnicodeError as e: - raise LnEncodeException("invalid url") from e + raise BOLT11EncodeException("invalid url") from e bech32_data = convertbits(url, 8, 5, True) assert bech32_data lnurl = bech32_encode( diff --git a/electrum/lnworker.py b/electrum/lnworker.py index e73fb5126..79421e639 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -64,7 +64,7 @@ from .lntransport import ( ConnStringFormatError ) from .lnpeer import Peer, LN_P2P_NETWORK_TIMEOUT -from .lnaddr import lnencode, LnAddr, lndecode +from .bolt11 import encode_bolt11_invoice, BOLT11Addr, decode_bolt11_invoice from .lnchannel import Channel, AbstractChannel, ChannelState, PeerState, HTLCWithStatus, ChannelBackup from .lnrater import LNRater from .lnutil import ( @@ -123,7 +123,7 @@ class PaymentInfo: - Historically, we used to store "bolt11, direction, status", but deserializing bolt11 was too slow. (even deserializing just once - all bolt11 during wallet-open - was slow) - - note: the deserialization code in lnaddr.py has been significantly sped up since + - note: the deserialization code in bolt11.py has been significantly sped up since - For incoming payments, for unpaid requests, ~every time the user displays the unpaid bolt11, we get a chance to display a new bolt11, with same payment_hash/amount, but with updated routing_hints (channels might get closed/opened, or just liquidity changed drastically). @@ -1930,7 +1930,7 @@ class LNWallet(Logger): f"pay_to_node starting session for RHASH={payment_hash.hex()}. " f"using_trampoline={self.uses_trampoline()}. " f"invoice_features={paysession.invoice_features.get_names()}. " - f"r_tags={LnAddr.format_bolt11_routing_info_as_human_readable(r_tags)}. " + f"r_tags={BOLT11Addr.format_bolt11_routing_info_as_human_readable(r_tags)}. " f"{amount_to_pay=} msat. {budget=}") if not self.uses_trampoline(): self.logger.info( @@ -2219,11 +2219,11 @@ class LNWallet(Logger): except Exception: return None - def _check_bolt11_invoice(self, bolt11_invoice: str, *, amount_msat: int = None) -> LnAddr: + def _check_bolt11_invoice(self, bolt11_invoice: str, *, amount_msat: int = None) -> BOLT11Addr: """Parses and validates a bolt11 invoice str into a LnAddr. Includes pre-payment checks external to the parser. """ - addr = lndecode(bolt11_invoice) + addr = decode_bolt11_invoice(bolt11_invoice) if addr.is_expired(): raise InvoiceError(_("This invoice has expired")) # check amount @@ -2563,7 +2563,7 @@ class LNWallet(Logger): message: str, fallback_address: Optional[str], channels: Optional[Sequence[Channel]] = None, - ) -> Tuple[LnAddr, str]: + ) -> Tuple[BOLT11Addr, str]: amount_msat = payment_info.amount_msat pair = self._bolt11_cache.get(payment_info.payment_hash) if pair: @@ -2580,12 +2580,12 @@ class LNWallet(Logger): # TODO: make invoice_features dynamic depending on available trampoline channels only_trampoline=payment_info.invoice_features.supports(LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT_ELECTRUM), ) - formatted_r_hints = LnAddr.format_bolt11_routing_info_as_human_readable(routing_hints, has_explicit_r_tagtype=True) + formatted_r_hints = BOLT11Addr.format_bolt11_routing_info_as_human_readable(routing_hints, has_explicit_r_tagtype=True) self.logger.info(f"creating bolt11 invoice with routing_hints: {formatted_r_hints}, sat: {(amount_msat or 0) // 1000}") payment_secret = self.get_payment_secret(payment_info.payment_hash) amount_btc = amount_msat/Decimal(COIN*1000) if amount_msat else None min_final_cltv_delta = payment_info.min_final_cltv_delta + MIN_FINAL_CLTV_DELTA_BUFFER_INVOICE - lnaddr = LnAddr( + lnaddr = BOLT11Addr( paymenthash=payment_info.payment_hash, amount=amount_btc, tags=[ @@ -2597,7 +2597,7 @@ class LNWallet(Logger): ] + routing_hints, date=timestamp, payment_secret=payment_secret) - invoice = lnencode(lnaddr, self.node_keypair.privkey) + invoice = encode_bolt11_invoice(lnaddr, self.node_keypair.privkey) pair = lnaddr, invoice self._bolt11_cache[payment_info.payment_hash] = pair return pair @@ -3971,7 +3971,7 @@ class LNWallet(Logger): invoice_features = payload["invoice_features"]["invoice_features"] invoice_routing_info = payload["invoice_routing_info"]["invoice_routing_info"] r_tags = decode_routing_info(invoice_routing_info) - self.logger.info(f'r_tags {LnAddr.format_bolt11_routing_info_as_human_readable(r_tags)}') + self.logger.info(f'r_tags {BOLT11Addr.format_bolt11_routing_info_as_human_readable(r_tags)}') # TODO legacy mpp payment, use total_msat from trampoline onion else: self.logger.info('forward_trampoline: end-to-end') diff --git a/electrum/payment_identifier.py b/electrum/payment_identifier.py index 198bb68a1..7fe8a2f1e 100644 --- a/electrum/payment_identifier.py +++ b/electrum/payment_identifier.py @@ -18,7 +18,7 @@ from .lnurl import (decode_lnurl, request_lnurl, callback_lnurl, LNURLError, lightning_address_to_url, try_resolve_lnurlpay, LNURL6Data, LNURL3Data, LNURLData, SUPPORTED_LNURL_SCHEMES) from .bitcoin import opcodes, construct_script -from .lnaddr import LnInvoiceException +from .bolt11 import BOLT11InvoiceException from .lnutil import IncompatibleOrInsaneFeatures from .bip21 import parse_bip21_URI, InvalidBitcoinURI, LIGHTNING_URI_SCHEME, BITCOIN_BIP21_URI_SCHEME from .segwit_addr import bech32_decode @@ -520,7 +520,7 @@ class PaymentIdentifier(Logger): error = _("Error parsing Lightning invoice") + f":\n{e!r}" if e.args and len(e.args): arg = e.args[0] - if isinstance(arg, LnInvoiceException): + if isinstance(arg, BOLT11InvoiceException): error = _("Error parsing Lightning invoice") + f":\n{e}" elif isinstance(arg, IncompatibleOrInsaneFeatures): error = _("Invoice requires unknown or incompatible Lightning feature") + f":\n{e!r}" diff --git a/electrum/plugins/watchtower/server.py b/electrum/plugins/watchtower/server.py index e18475321..2f526f839 100644 --- a/electrum/plugins/watchtower/server.py +++ b/electrum/plugins/watchtower/server.py @@ -8,7 +8,7 @@ from aiohttp import web from electrum.util import log_exceptions, ignore_exceptions from electrum.logging import Logger from electrum.util import EventListener -from electrum.lnaddr import lndecode +from electrum.bolt11 import decode_bolt11_invoice from electrum.daemon import AuthenticatedServer diff --git a/electrum/submarine_swaps.py b/electrum/submarine_swaps.py index ef55aa632..37d8d2afe 100644 --- a/electrum/submarine_swaps.py +++ b/electrum/submarine_swaps.py @@ -38,7 +38,7 @@ from .util import ( ) from . import lnutil from .lnutil import hex_to_bytes, REDEEM_AFTER_DOUBLE_SPENT_DELAY, Keypair -from .lnaddr import lndecode +from .bolt11 import decode_bolt11_invoice from .json_db import StoredObject, stored_in from . import constants from .address_synchronizer import (TX_HEIGHT_LOCAL, TX_HEIGHT_FUTURE, TX_HEIGHT_UNCONFIRMED, @@ -997,7 +997,7 @@ class SwapManager(Logger): } await transport.send_request_to_server('addswapinvoice', request_data) # wait for funding tx - lnaddr = lndecode(invoice) + lnaddr = decode_bolt11_invoice(invoice) while swap.funding_txid is None and not lnaddr.is_expired(): await asyncio.sleep(0.1) return swap.funding_txid diff --git a/electrum/wallet_db.py b/electrum/wallet_db.py index a5d146a00..d00883b91 100644 --- a/electrum/wallet_db.py +++ b/electrum/wallet_db.py @@ -905,7 +905,7 @@ class WalletDBUpgrader(Logger): self.data['seed_version'] = 44 def _convert_version_45(self): - from .lnaddr import lndecode + from .bolt11 import decode_bolt11_invoice if not self._is_upgrade_method_needed(44, 44): return swaps = self.data.get('submarine_swaps', {}) @@ -921,7 +921,7 @@ class WalletDBUpgrader(Logger): outputs = item['outputs'] if not is_lightning else None bip70 = item['bip70'] if not is_lightning else None if is_lightning: - lnaddr = lndecode(item['invoice']) + lnaddr = decode_bolt11_invoice(item['invoice']) amount_msat = lnaddr.get_amount_msat() timestamp = lnaddr.date exp_delay = lnaddr.get_expiry() @@ -974,7 +974,7 @@ class WalletDBUpgrader(Logger): self.data['seed_version'] = 46 def _convert_version_47(self): - from .lnaddr import lndecode + from .bolt11 import decode_bolt11_invoice if not self._is_upgrade_method_needed(46, 46): return # recalc keys of requests @@ -982,7 +982,7 @@ class WalletDBUpgrader(Logger): for key, item in list(requests.items()): lnaddr = item.get('lightning_invoice') if lnaddr: - lnaddr = lndecode(lnaddr) + lnaddr = decode_bolt11_invoice(lnaddr) rhash = lnaddr.paymenthash.hex() if key != rhash: requests[rhash] = item @@ -1023,7 +1023,7 @@ class WalletDBUpgrader(Logger): self.data['seed_version'] = 50 def _convert_version_51(self): - from .lnaddr import lndecode + from .bolt11 import decode_bolt11_invoice if not self._is_upgrade_method_needed(50, 50): return requests = self.data.get('payment_requests', {}) @@ -1032,7 +1032,7 @@ class WalletDBUpgrader(Logger): if lightning_invoice is None: payment_hash = None else: - lnaddr = lndecode(lightning_invoice) + lnaddr = decode_bolt11_invoice(lightning_invoice) payment_hash = lnaddr.paymenthash.hex() item['payment_hash'] = payment_hash self.data['seed_version'] = 51 diff --git a/tests/test_bolt11.py b/tests/test_bolt11.py index a900545f5..01453a87b 100644 --- a/tests/test_bolt11.py +++ b/tests/test_bolt11.py @@ -4,7 +4,7 @@ from binascii import unhexlify, hexlify import pprint import unittest -from electrum.lnaddr import shorten_amount, unshorten_amount, LnAddr, lnencode, lndecode +from electrum.bolt11 import shorten_amount, unshorten_amount, BOLT11Addr, encode_bolt11_invoice, decode_bolt11_invoice from electrum.segwit_addr import bech32_encode, bech32_decode from electrum import segwit_addr from electrum.lnutil import UnknownEvenFeatureBits, LnFeatures, IncompatibleLightningFeatures @@ -67,106 +67,106 @@ class TestBolt11(ElectrumTestCase): timestamp = 1615922274 tests = [ - (LnAddr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, tags=[('d', ''), ('9', 33282)]), + (BOLT11Addr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, tags=[('d', ''), ('9', 33282)]), "lnbc1ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsdqq9qypqszpyrpe4tym8d3q87d43cgdhhlsrt78epu7u99mkzttmt2wtsx0304rrw50addkryfrd3vn3zy467vxwlmf4uz7yvntuwjr2hqjl9lw5cqwtp2dy"), - (LnAddr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=Decimal('0.001'), tags=[('d', '1 cup coffee'), ('x', 60), ('9', 0x28200)]), + (BOLT11Addr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=Decimal('0.001'), tags=[('d', '1 cup coffee'), ('x', 60), ('9', 0x28200)]), "lnbc1m1ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsdq5xysxxatsyp3k7enxv4jsxqzpu9qy9qsqw8l2pulslacwjt86vle3sgfdmcct5v34gtcpfnujsf6ufqa7v7jzdpddnwgte82wkscdlwfwucrgn8z36rv9hzk5mukltteh0yqephqpk5vegu"), - (LnAddr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=Decimal('1'), tags=[('h', longdescription), ('9', 0x28200)]), + (BOLT11Addr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=Decimal('1'), tags=[('h', longdescription), ('9', 0x28200)]), "lnbc11ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygshp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qy9qsq0jnua6dc4p984aeafs6ss7tjjj7553ympvg82qrjq0zgdqgtdvt5wlwkvw4ds5sn96nazp6ct9ts37tcw708kzkk4p8znahpsgp9tnspnycsf7"), - (LnAddr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, net=constants.BitcoinTestnet, tags=[('f', 'mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP'), ('h', longdescription), ('9', 0x28200)]), + (BOLT11Addr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, net=constants.BitcoinTestnet, tags=[('f', 'mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP'), ('h', longdescription), ('9', 0x28200)]), "lntb1ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsfpp3x9et2e20v6pu37c5d9vax37wxq72un98hp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qy9qsqy5826t0z3sn29z396pmr4kv73lcx0v7y6vas6h3pysmqllmzwgm5ps2t468gm4psj52usjy6y4xcry4k84n2zggs6f9agwg95454v6gqrwmh4f"), - (LnAddr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=24, tags=[ + (BOLT11Addr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=24, tags=[ ('r', [(unhexlify('029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255'), unhexlify('0102030405060708'), 1, 20, 3), (unhexlify('039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255'), unhexlify('030405060708090a'), 2, 30, 4)]), ('f', '1RustyRX2oai4EYYDpQGWvEL62BBGqN9T'), ('h', longdescription), ('9', 0x28200)]), "lnbc241ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsr9yq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpeuqafqxu92d8lr6fvg0r5gv0heeeqgcrqlnm6jhphu9y00rrhy4grqszsvpcgpy9qqqqqqgqqqqq7qqzqfpp3qjmp7lwpagxun9pygexvgpjdc4jdj85fhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qy9qsqfnk063vsrgjx7l6td6v42skuxql7epn5tmrl4qte2e78nqnsjlgjg3sgkxreqex5fw4c9chnvtc2hykqnyxr84zwfr8f3d9q3h0nfdgqenlzvj"), - (LnAddr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=24, tags=[('f', '3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX'), ('h', longdescription), ('9', 0x28200)]), + (BOLT11Addr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=24, tags=[('f', '3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX'), ('h', longdescription), ('9', 0x28200)]), "lnbc241ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsfppj3a24vwu6r8ejrss3axul8rxldph2q7z9hp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qy9qsqqf6z4r7ruzr5txm5ln4netwa2f4x233tud7jy8gxrynyx07rxt7qm92yk2krlgwr7d8jknglur75sujeyapmda5nf3femrk2mep8a2cp4hlvup"), - (LnAddr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=24, tags=[('f', 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'), ('h', longdescription), ('9', 0x28200)]), + (BOLT11Addr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=24, tags=[('f', 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'), ('h', longdescription), ('9', 0x28200)]), "lnbc241ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsfppqw508d6qejxtdg4y5r3zarvary0c5xw7khp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qy9qsqy4wp73jma5uktd9y7yha56f98n2k0hxgnvp2qdcury00dapps3k3urgfy8tvv8jzwcafpy576msk5xx2hladf06m3s5mgx5msn4elfqqaaqjhk"), - (LnAddr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=24, tags=[('f', 'bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3'), ('h', longdescription), ('9', 0x28200)]), + (BOLT11Addr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=24, tags=[('f', 'bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3'), ('h', longdescription), ('9', 0x28200)]), "lnbc241ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsfp4qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qy9qsqgt4gg9uktlpgnnuvczazusp5uwjv78na305ucsw06c8uk58e5stjqj9sz7fgavw0z688alt364js72mc9mg8yumhpes2dsmq5k9nr5qqddykxy"), - (LnAddr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=24, tags=[('n', PUBKEY), ('h', longdescription), ('9', 0x28200)]), + (BOLT11Addr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=24, tags=[('n', PUBKEY), ('h', longdescription), ('9', 0x28200)]), "lnbc241ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66hp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qy9qsq2y235rxw7v0gkn2t9ehc742tm3p22q2yjjykq4d85ze6g62yk60navxqz0ga96sqrszju8nlfajthem4gngxvyz4hwy39j4nqm8kv0qq9znxs7"), - (LnAddr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=24, tags=[('h', longdescription), ('9', 2 + (1 << 9) + (1 << 15))]), + (BOLT11Addr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=24, tags=[('h', longdescription), ('9', 2 + (1 << 9) + (1 << 15))]), "lnbc241ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygshp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qypqszrwfgrl5k3rt4q4mclc8t00p2tcjsf9pmpcq6lu5zhmampyvk43fk30eqpdm8t5qmdpzan25aqxqaqdzmy0smrtduazjcxx975vz78ccpx0qhev"), - (LnAddr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=24, tags=[('h', longdescription), ('9', 10 + (1 << 8) + (1 << 15))]), + (BOLT11Addr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=24, tags=[('h', longdescription), ('9', 10 + (1 << 8) + (1 << 15))]), "lnbc241ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygshp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qypqg2wans8f6vkfd3l7zjv547hlc7wd7eqyxfwhtdudnkkgrpk6p9ffykwrvdtwm0aakaxujurdxgd7cllnfypmj22cvy7z333udg6zncgacqzmd2z9"), - (LnAddr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=24, tags=[('h', longdescription), ('9', 10 + (1 << 9) + (1 << 15))]), + (BOLT11Addr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=24, tags=[('h', longdescription), ('9', 10 + (1 << 9) + (1 << 15))]), "lnbc241ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygshp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qypqs2dr525u5f4kjxdv0hq5c822qwxrtttjl4u586yl84x0kvvx66gz9ygy76005s5sjwgr7fp55ccsae47vpl4gqvwhc3exps964g743j5gqwtt68t"), - (LnAddr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=24, tags=[('h', longdescription), ('9', 10 + (1 << 9) + (1 << 14))]), + (BOLT11Addr(date=timestamp, paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=24, tags=[('h', longdescription), ('9', 10 + (1 << 9) + (1 << 14))]), "lnbc241ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygshp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qrss2f8kr98446xls02yndup2ynwjh46u8kdeuuncexx2hnets0j0064nyq25gkd6jnttldzt5qqtszum5dufvuvryxt204w2p24557udxgcp0nlwtw"), ] # Some old tests follow that do not have payment_secret. Note that if the parser raised due to the lack of features/payment_secret, # old wallets that have these invoices saved (as paid/expired), could not be opened (though we could do a db upgrade and delete them). tests.extend([ - (LnAddr(date=timestamp, paymenthash=RHASH, tags=[('d', '')]), + (BOLT11Addr(date=timestamp, paymenthash=RHASH, tags=[('d', '')]), "lnbc1ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdqqd9n3kwjjwglnfne5p4rvkze998m3xcxrc8kunl5khkchlaqhwhlyztuuwkrglv47mqg96mcqjjx70hh9luaj4te0u4ww6aclxwve3fqpkmdxlj"), - (LnAddr(date=timestamp, paymenthash=RHASH, amount=Decimal('0.001'), tags=[('d', '1 cup coffee'), ('x', 60)]), + (BOLT11Addr(date=timestamp, paymenthash=RHASH, amount=Decimal('0.001'), tags=[('d', '1 cup coffee'), ('x', 60)]), "lnbc1m1ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpu9rflz25dx0qw6kdg05u0c5hdc30yq6ga6ew4pz86n244va45nchns9zrs3wjxznsqnt37hz7pswvc56wvuhxcjyd6k3lqf4ujynyxuspmvr078"), - (LnAddr(date=timestamp, paymenthash=RHASH, amount=Decimal('1'), tags=[('h', longdescription)]), + (BOLT11Addr(date=timestamp, paymenthash=RHASH, amount=Decimal('1'), tags=[('h', longdescription)]), "lnbc11ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs2qjafckq94q3js6lvqz2kmenn9ysjejyj8fm4hlx0xtqhaxfzlxjappkgp0hmm40dnuan4v3jy83lqjup2n0fdzgysg049y9l9uc98qq07kfd3"), - (LnAddr(date=timestamp, paymenthash=RHASH, net=constants.BitcoinTestnet, tags=[('f', 'mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP'), ('h', longdescription)]), + (BOLT11Addr(date=timestamp, paymenthash=RHASH, net=constants.BitcoinTestnet, tags=[('f', 'mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP'), ('h', longdescription)]), "lntb1ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfpp3x9et2e20v6pu37c5d9vax37wxq72un98hp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsr9zktgu78k8p9t8555ve37qwfvqn6ga37fnfwhgexmf20nzdpmuhwvuv7zra3xrh8y2ggxxuemqfsgka9x7uzsrcx8rfv85c8pmhq9gq4sampn"), ]) # Roundtrip for lnaddr1, invoice_str1 in tests: - invoice_str2 = lnencode(lnaddr1, PRIVKEY) + invoice_str2 = encode_bolt11_invoice(lnaddr1, PRIVKEY) self.assertEqual(invoice_str1, invoice_str2) - lnaddr2 = lndecode(invoice_str2, net=lnaddr1.net) + lnaddr2 = decode_bolt11_invoice(invoice_str2, net=lnaddr1.net) self.compare(lnaddr1, lnaddr2) def test_n_decoding(self): # We flip the signature recovery bit, which would normally give a different # pubkey. _, hrp, data = bech32_decode( - lnencode(LnAddr(paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=24, tags=[('d', ''), ('9', 33282)]), PRIVKEY), + encode_bolt11_invoice(BOLT11Addr(paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=24, tags=[('d', ''), ('9', 33282)]), PRIVKEY), ignore_long_length=True) data[-1] ^= 1 - lnaddr = lndecode(bech32_encode(segwit_addr.Encoding.BECH32, hrp, data), verbose=True) + lnaddr = decode_bolt11_invoice(bech32_encode(segwit_addr.Encoding.BECH32, hrp, data), verbose=True) self.assertNotEqual(lnaddr.pubkey.serialize(), PUBKEY) # But not if we supply expliciy `n` specifier! _, hrp, data = bech32_decode( - lnencode(LnAddr(paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=24, tags=[('d', ''), ('n', PUBKEY), ('9', 33282)]), PRIVKEY), + encode_bolt11_invoice(BOLT11Addr(paymenthash=RHASH, payment_secret=PAYMENT_SECRET, amount=24, tags=[('d', ''), ('n', PUBKEY), ('9', 33282)]), PRIVKEY), ignore_long_length=True) data[-1] ^= 1 - lnaddr = lndecode(bech32_encode(segwit_addr.Encoding.BECH32, hrp, data), verbose=True) + lnaddr = decode_bolt11_invoice(bech32_encode(segwit_addr.Encoding.BECH32, hrp, data), verbose=True) self.assertEqual(lnaddr.pubkey.serialize(), PUBKEY) def test_min_final_cltv_expiry_decoding(self): - lnaddr = lndecode("lnsb500u1pdsgyf3pp5nmrqejdsdgs4n9ukgxcp2kcq265yhrxd4k5dyue58rxtp5y83s3qsp5qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsdqqcqzys9qypqsqp2h6a5xeytuc3fad2ed4gxvhd593lwjdna3dxsyeem0qkzjx6guk44jend0xq4zzvp6f3fy07wnmxezazzsxgmvqee8shxjuqu2eu0qpnvc95x", - net=constants.BitcoinSimnet) + lnaddr = decode_bolt11_invoice("lnsb500u1pdsgyf3pp5nmrqejdsdgs4n9ukgxcp2kcq265yhrxd4k5dyue58rxtp5y83s3qsp5qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsdqqcqzys9qypqsqp2h6a5xeytuc3fad2ed4gxvhd593lwjdna3dxsyeem0qkzjx6guk44jend0xq4zzvp6f3fy07wnmxezazzsxgmvqee8shxjuqu2eu0qpnvc95x", + net=constants.BitcoinSimnet) self.assertEqual(144, lnaddr.get_min_final_cltv_delta()) - lnaddr = lndecode("lntb15u1p0m6lzupp5zqjthgvaad9mewmdjuehwddyze9d8zyxcc43zhaddeegt37sndgsdq4xysyymr0vd4kzcmrd9hx7cqp7xqrrss9qy9qsqsp5vlhcs24hwm747w8f3uau2tlrdkvjaglffnsstwyamj84cxuhrn2s8tut3jqumepu42azyyjpgqa4w9w03204zp9h4clk499y2umstl6s29hqyj8vv4as6zt5567ux7l3f66m8pjhk65zjaq2esezk7ll2kcpljewkg", - net=constants.BitcoinTestnet) + lnaddr = decode_bolt11_invoice("lntb15u1p0m6lzupp5zqjthgvaad9mewmdjuehwddyze9d8zyxcc43zhaddeegt37sndgsdq4xysyymr0vd4kzcmrd9hx7cqp7xqrrss9qy9qsqsp5vlhcs24hwm747w8f3uau2tlrdkvjaglffnsstwyamj84cxuhrn2s8tut3jqumepu42azyyjpgqa4w9w03204zp9h4clk499y2umstl6s29hqyj8vv4as6zt5567ux7l3f66m8pjhk65zjaq2esezk7ll2kcpljewkg", + net=constants.BitcoinTestnet) self.assertEqual(30, lnaddr.get_min_final_cltv_delta()) def test_min_final_cltv_expiry_roundtrip(self): for cltv in (1, 15, 16, 31, 32, 33, 150, 511, 512, 513, 1023, 1024, 1025): - lnaddr = LnAddr( + lnaddr = BOLT11Addr( paymenthash=RHASH, payment_secret=b"\x01"*32, amount=Decimal('0.001'), tags=[('d', '1 cup coffee'), ('x', 60), ('c', cltv), ('9', 33282)]) self.assertEqual(cltv, lnaddr.get_min_final_cltv_delta()) - invoice = lnencode(lnaddr, PRIVKEY) - self.assertEqual(cltv, lndecode(invoice).get_min_final_cltv_delta()) + invoice = encode_bolt11_invoice(lnaddr, PRIVKEY) + self.assertEqual(cltv, decode_bolt11_invoice(invoice).get_min_final_cltv_delta()) def test_features(self): - lnaddr = lndecode("lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsdq5vdhkven9v5sxyetpdees9qypqsztrz5v3jfnxskfv7g8chmyzyrfhf2vupcavuq5rce96kyt6g0zh337h206awccwp335zarqrud4wccgdn39vur44d8um4hmgv06aj0sgpdrv73z") + lnaddr = decode_bolt11_invoice("lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsdq5vdhkven9v5sxyetpdees9qypqsztrz5v3jfnxskfv7g8chmyzyrfhf2vupcavuq5rce96kyt6g0zh337h206awccwp335zarqrud4wccgdn39vur44d8um4hmgv06aj0sgpdrv73z") self.assertEqual(33282, lnaddr.get_tag('9')) self.assertEqual(LnFeatures(33282), lnaddr.get_features()) def test_payment_secret(self): - lnaddr = lndecode("lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsdq5vdhkven9v5sxyetpdees9q5sqqqqqqqqqqqqqqqpqsqvvh7ut50r00p3pg34ea68k7zfw64f8yx9jcdk35lh5ft8qdr8g4r0xzsdcrmcy9hex8un8d8yraewvhqc9l0sh8l0e0yvmtxde2z0hgpzsje5l") + lnaddr = decode_bolt11_invoice("lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsdq5vdhkven9v5sxyetpdees9q5sqqqqqqqqqqqqqqqpqsqvvh7ut50r00p3pg34ea68k7zfw64f8yx9jcdk35lh5ft8qdr8g4r0xzsdcrmcy9hex8un8d8yraewvhqc9l0sh8l0e0yvmtxde2z0hgpzsje5l") self.assertEqual((1 << 9) + (1 << 15) + (1 << 99), lnaddr.get_tag('9')) self.assertEqual(b"\x11" * 32, lnaddr.payment_secret) def test_validate_and_compare_features(self): - lnaddr = lndecode("lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsdq5vdhkven9v5sxyetpdees9q5sqqqqqqqqqqqqqqqpqsqvvh7ut50r00p3pg34ea68k7zfw64f8yx9jcdk35lh5ft8qdr8g4r0xzsdcrmcy9hex8un8d8yraewvhqc9l0sh8l0e0yvmtxde2z0hgpzsje5l") + lnaddr = decode_bolt11_invoice("lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsdq5vdhkven9v5sxyetpdees9q5sqqqqqqqqqqqqqqqpqsqvvh7ut50r00p3pg34ea68k7zfw64f8yx9jcdk35lh5ft8qdr8g4r0xzsdcrmcy9hex8un8d8yraewvhqc9l0sh8l0e0yvmtxde2z0hgpzsje5l") lnaddr.validate_and_compare_features(LnFeatures((1 << 8) + (1 << 14) + (1 << 15))) with self.assertRaises(IncompatibleLightningFeatures): lnaddr.validate_and_compare_features(LnFeatures((1 << 8) + (1 << 14) + (1 << 16))) @@ -188,7 +188,7 @@ class TestBolt11(ElectrumTestCase): '16000000x0x717', 0, 1, 40),] ), ], - LnAddr.format_bolt11_routing_info_as_human_readable(r_tags_expl, has_explicit_r_tagtype=True)) + BOLT11Addr.format_bolt11_routing_info_as_human_readable(r_tags_expl, has_explicit_r_tagtype=True)) r_tags_impl = [ [(bfh('029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255'), bfh('0102030405060708'), 1, 20, 3), @@ -204,9 +204,9 @@ class TestBolt11(ElectrumTestCase): [('038863cf8ab91046230f561cd5b386cbff8309fa02e3f0c3ed161a3aeb64a643b9', '16000000x0x717', 0, 1, 40),], ], - LnAddr.format_bolt11_routing_info_as_human_readable(r_tags_impl, has_explicit_r_tagtype=False)) + BOLT11Addr.format_bolt11_routing_info_as_human_readable(r_tags_impl, has_explicit_r_tagtype=False)) for has_explicit_r_tagtype in (False, True): self.assertEqual( [], - LnAddr.format_bolt11_routing_info_as_human_readable([], has_explicit_r_tagtype=has_explicit_r_tagtype)) + BOLT11Addr.format_bolt11_routing_info_as_human_readable([], has_explicit_r_tagtype=has_explicit_r_tagtype)) diff --git a/tests/test_commands.py b/tests/test_commands.py index 69b833b7a..f294c6da5 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -20,7 +20,7 @@ from electrum.submarine_swaps import SwapOffer, SwapFees, NostrTransport from electrum.transaction import Transaction, TxOutput, tx_from_any from electrum.util import UserFacingException, NotEnoughFunds from electrum.crypto import sha256 -from electrum.lnaddr import lndecode +from electrum.bolt11 import decode_bolt11_invoice from electrum.daemon import Daemon from electrum import json_db @@ -508,7 +508,7 @@ class TestCommandsTestnet(ElectrumTestCase): expiry=3500, wallet=wallet, ) - invoice = lndecode(invoice=result['invoice']) + invoice = decode_bolt11_invoice(invoice=result['invoice']) assert invoice.paymenthash.hex() == payment_hash assert wallet.lnworker.get_payment_info(bytes.fromhex(payment_hash), direction=RECEIVED) assert payment_hash in wallet.lnworker.dont_expire_htlcs diff --git a/tests/test_lnpeer.py b/tests/test_lnpeer.py index 6dc07e561..cf06af5df 100644 --- a/tests/test_lnpeer.py +++ b/tests/test_lnpeer.py @@ -29,7 +29,7 @@ from electrum import constants from electrum import bip32 from electrum.network import Network, ProxySettings from electrum import simple_config, lnutil -from electrum.lnaddr import lnencode, LnAddr, lndecode +from electrum.bolt11 import encode_bolt11_invoice, BOLT11Addr, decode_bolt11_invoice from electrum.bitcoin import COIN, sha256 from electrum.transaction import Transaction from electrum.util import NetworkRetryManager, bfh, OldTaskGroup, EventListener, InvoiceError @@ -179,7 +179,7 @@ class MockLNWallet(LNWallet): self.channel_db.stop() await self.channel_db.stopped_event.wait() - async def create_routes_from_invoice(self, amount_msat: int, decoded_invoice: LnAddr, *, full_path=None): + async def create_routes_from_invoice(self, amount_msat: int, decoded_invoice: BOLT11Addr, *, full_path=None): paysession = PaySession( payment_hash=decoded_invoice.paymenthash, payment_secret=decoded_invoice.payment_secret, @@ -419,7 +419,7 @@ class TestPeer(ElectrumTestCase): invoice_features: LnFeatures = None, min_final_cltv_delta: int = None, expiry: int = None, - ) -> Tuple[LnAddr, Invoice]: + ) -> Tuple[BOLT11Addr, Invoice]: amount_btc = amount_msat/Decimal(COIN*1000) if payment_preimage is None and not payment_hash: payment_preimage = os.urandom(32) @@ -450,7 +450,7 @@ class TestPeer(ElectrumTestCase): invoice_features=invoice_features, ) w2.save_payment_info(info) - lnaddr1 = LnAddr( + lnaddr1 = BOLT11Addr( paymenthash=payment_hash, amount=amount_btc, tags=[ @@ -461,8 +461,8 @@ class TestPeer(ElectrumTestCase): ] + routing_hints, payment_secret=payment_secret, ) - invoice = lnencode(lnaddr1, w2.node_keypair.privkey) - lnaddr2 = lndecode(invoice) # unlike lnaddr1, this now has a pubkey set + invoice = encode_bolt11_invoice(lnaddr1, w2.node_keypair.privkey) + lnaddr2 = decode_bolt11_invoice(invoice) # unlike lnaddr1, this now has a pubkey set return lnaddr2, Invoice.from_bech32(invoice) async def _activate_trampoline(self, w: MockLNWallet): @@ -1052,7 +1052,7 @@ class TestPeerDirect(TestPeer): self.assertEqual(PR_UNPAID, w2.get_payment_status(lnaddr.paymenthash, direction=RECEIVED)) assert lnaddr.get_min_final_cltv_delta() == 400 # what the receiver expects lnaddr.tags = [tag for tag in lnaddr.tags if tag[0] != 'c'] + [['c', 144]] - b11 = lnencode(lnaddr, w2.node_keypair.privkey) + b11 = encode_bolt11_invoice(lnaddr, w2.node_keypair.privkey) pay_req = Invoice.from_bech32(b11) assert pay_req._lnaddr.get_min_final_cltv_delta() == 144 # what w1 will use to pay result, log = await w1.pay_invoice(pay_req) @@ -1095,7 +1095,7 @@ class TestPeerDirect(TestPeer): # create lightning invoice in the past, so it is expired with mock.patch('time.time', return_value=int(time.time()) - 10000): lnaddr, _pay_req = self.prepare_invoice(w2, expiry=3600) - b11 = lnencode(lnaddr, w2.node_keypair.privkey) + b11 = encode_bolt11_invoice(lnaddr, w2.node_keypair.privkey) pay_req = Invoice.from_bech32(b11) async def try_pay_expired_invoice(pay_req: Invoice, w1=w1): @@ -2044,7 +2044,7 @@ class TestPeerDirect(TestPeer): } # create 10 invoices (10 pending htlc sets with 1 htlc each) - invoices = [] # type: list[tuple[LnAddr, Invoice]] + invoices = [] # type: list[tuple[BOLT11Addr, Invoice]] for i in range(10): lnaddr, pay_req = self.prepare_invoice(bob_w) # prevent bob from settling so that htlc switch will have to iterate through all pending htlcs From 560d90e8b8bef9ae9624a030bff55630a5eff13a Mon Sep 17 00:00:00 2001 From: f321x Date: Mon, 27 Apr 2026 16:24:18 +0200 Subject: [PATCH 2/3] qt, watchtower: cleanup imports --- electrum/gui/qt/channel_details.py | 12 ++++-------- electrum/plugins/watchtower/server.py | 7 ------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/electrum/gui/qt/channel_details.py b/electrum/gui/qt/channel_details.py index 3369dd10d..42da866d0 100644 --- a/electrum/gui/qt/channel_details.py +++ b/electrum/gui/qt/channel_details.py @@ -3,16 +3,12 @@ from typing import TYPE_CHECKING, Sequence import PyQt6.QtGui as QtGui import PyQt6.QtWidgets as QtWidgets import PyQt6.QtCore as QtCore -from PyQt6.QtWidgets import QLabel, QLineEdit, QHBoxLayout, QGridLayout +from PyQt6.QtWidgets import QLabel, QHBoxLayout -from electrum.util import EventListener, ShortID +from electrum.util import ShortID from electrum.i18n import _ -from electrum.util import format_time -from electrum.lnutil import format_short_channel_id, LOCAL, REMOTE, UpdateAddHtlc, Direction -from electrum.lnchannel import htlcsum, Channel, AbstractChannel, HTLCWithStatus -from electrum.bolt11 import BOLT11Addr, decode_bolt11_invoice -from electrum.bitcoin import COIN -from electrum.wallet import Abstract_Wallet +from electrum.lnutil import LOCAL, REMOTE, UpdateAddHtlc, Direction +from electrum.lnchannel import Channel, AbstractChannel, HTLCWithStatus from electrum.gui.common_qt.util import QtEventListener, qt_event_listener from .util import Buttons, CloseButton, ShowQRLineEdit, MessageBoxMixin, WWLabel, VLine diff --git a/electrum/plugins/watchtower/server.py b/electrum/plugins/watchtower/server.py index 2f526f839..ddc0525ba 100644 --- a/electrum/plugins/watchtower/server.py +++ b/electrum/plugins/watchtower/server.py @@ -1,14 +1,7 @@ -import os -import asyncio -from collections import defaultdict from typing import TYPE_CHECKING from aiohttp import web -from electrum.util import log_exceptions, ignore_exceptions -from electrum.logging import Logger -from electrum.util import EventListener -from electrum.bolt11 import decode_bolt11_invoice from electrum.daemon import AuthenticatedServer From b0a5e2010cf496af6fd62a3fd145c182e93e869f Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 27 Apr 2026 15:25:36 +0000 Subject: [PATCH 3/3] bolt11: follow-up renames --- electrum/bolt11.py | 7 ++++--- electrum/lnworker.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/electrum/bolt11.py b/electrum/bolt11.py index 0d910a919..a532c444e 100644 --- a/electrum/bolt11.py +++ b/electrum/bolt11.py @@ -332,7 +332,8 @@ class BOLT11Addr: def validate_and_compare_features(self, myfeatures: 'LnFeatures') -> None: """Raises IncompatibleOrInsaneFeatures. - note: these checks are not done by the parser (in lndecode), as then when we started requiring a new feature, + note: these checks are not done by the parser (in decode_bolt11_invoice), + as then when we started requiring a new feature, old saved already paid invoices could no longer be parsed. """ from .lnutil import validate_features, ln_compare_features @@ -404,8 +405,8 @@ class SerializableKey: def decode_bolt11_invoice(invoice: str, *, verbose=False, net=None) -> BOLT11Addr: - """Parses a string into an LnAddr object. - Can raise LnDecodeException or IncompatibleOrInsaneFeatures. + """Parses a string into a BOLT11Addr object. + Can raise BOLT11DecodeException or IncompatibleOrInsaneFeatures. """ if net is None: net = constants.net diff --git a/electrum/lnworker.py b/electrum/lnworker.py index 79421e639..622f5bcf2 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -2220,7 +2220,7 @@ class LNWallet(Logger): return None def _check_bolt11_invoice(self, bolt11_invoice: str, *, amount_msat: int = None) -> BOLT11Addr: - """Parses and validates a bolt11 invoice str into a LnAddr. + """Parses and validates a bolt11 invoice str into a BOLT11Addr. Includes pre-payment checks external to the parser. """ addr = decode_bolt11_invoice(bolt11_invoice)