def lndecode(invoice: str, *, verbose=False, net=None) -> LnAddr:
    """Parses a string into an LnAddr object.
    Can raise LnDecodeException or IncompatibleOrInsaneFeatures.
    """
    if net is None:
        net = constants.net
    decoded_bech32 = bech32_decode(invoice, ignore_long_length=True)
    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")
    if decoded_bech32.encoding != segwit_addr.Encoding.BECH32:
        raise LnDecodeException("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")

    if not hrp[2:].startswith(net.BOLT11_HRP):
        raise LnDecodeException(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")
    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.pubkey = None
    addr.net = net

    amountstr = hrp[2+len(net.BOLT11_HRP):]
    # BOLT #11:
    #
    # A reader SHOULD indicate if amount is unspecified, otherwise it MUST
    # multiply `amount` by the `multiplier` value (if any) to derive the
    # amount required for payment.
    if amountstr != '':
        addr.amount = unshorten_amount(amountstr)

    addr.date = int_from_data5(data5_remaining[:7])
    data5_remaining = data5_remaining[7:]

    while data5_remaining:
        tag, tagdata = pull_tagged(data5_remaining)  # mutates arg

        # BOLT #11:
        #
        # A reader MUST skip over unknown fields, an `f` field with unknown
        # `version`, or a `p`, `h`, or `n` field which does not have
        # `data_length` 52, 52, or 53 respectively.
        data_length = len(tagdata)

        if tag == 'r':
            # BOLT #11:
            #
            # * `r` (3): `data_length` variable.  One or more entries
            # containing extra routing information for a private route;
            # there may be more than one `r` field, too.
            #    * `pubkey` (264 bits)
            #    * `short_channel_id` (64 bits)
            #    * `feebase` (32 bits, big-endian)
            #    * `feerate` (32 bits, big-endian)
            #    * `cltv_expiry_delta` (16 bits, big-endian)
            tagdata = convertbits(tagdata, 5, 8, False)
            if not tagdata:
                continue
            route = []
            with io.BytesIO(bytes(tagdata)) as s:
                while True:
                    pubkey = s.read(33)
                    scid = s.read(8)
                    feebase = s.read(4)
                    feerate = s.read(4)
                    cltv = s.read(2)
                    if len(cltv) != 2:
                        break  # EOF
                    feebase = int.from_bytes(feebase, byteorder="big")
                    feerate = int.from_bytes(feerate, byteorder="big")
                    cltv = int.from_bytes(cltv, byteorder="big")
                    route.append((pubkey, scid, feebase, feerate, cltv))
            if route:
                addr.tags.append(('r',route))
        elif tag == 't':
            tagdata = convertbits(tagdata, 5, 8, False)
            if not tagdata:
                continue
            route = []
            with io.BytesIO(bytes(tagdata)) as s:
                pubkey = s.read(33)
                feebase = s.read(4)
                feerate = s.read(4)
                cltv = s.read(2)
                if len(cltv) == 2:  # no EOF
                    feebase = int.from_bytes(feebase, byteorder="big")
                    feerate = int.from_bytes(feerate, byteorder="big")
                    cltv = int.from_bytes(cltv, byteorder="big")
                    route.append((pubkey, feebase, feerate, cltv))
            addr.tags.append(('t', route))
        elif tag == 'f':
            fallback = parse_fallback_addr(tagdata, addr.net)
            if fallback:
                addr.tags.append(('f', fallback))
            else:
                # Incorrect version.
                addr.unknown_tags.append((tag, tagdata))
                continue

        elif tag == 'd':
            addr.tags.append(('d', bytes(convertbits(tagdata, 5, 8, False)).decode('utf-8')))

        elif tag == 'h':
            if data_length != 52:
                addr.unknown_tags.append((tag, tagdata))
                continue
            addr.tags.append(('h', bytes(convertbits(tagdata, 5, 8, False))))

        elif tag == 'x':
            addr.tags.append(('x', int_from_data5(tagdata)))

        elif tag == 'p':
            if data_length != 52:
                addr.unknown_tags.append((tag, tagdata))
                continue
            addr.paymenthash = bytes(convertbits(tagdata, 5, 8, False))

        elif tag == 's':
            if data_length != 52:
                addr.unknown_tags.append((tag, tagdata))
                continue
            addr.payment_secret = bytes(convertbits(tagdata, 5, 8, False))

        elif tag == 'n':
            if data_length != 53:
                addr.unknown_tags.append((tag, tagdata))
                continue
            pubkeybytes = bytes(convertbits(tagdata, 5, 8, False))
            addr.pubkey = pubkeybytes

        elif tag == 'c':
            addr.tags.append(('c', int_from_data5(tagdata)))

        elif tag == '9':
            features = int_from_data5(tagdata)
            addr.tags.append(('9', features))
            # note: The features are not validated here in the parser,
            #       instead, validation is done just before we try paying the invoice (in lnworker._check_bolt11_invoice).
            #       Context: invoice parsing happens when opening a wallet. If there was a backwards-incompatible
            #       change to a feature, and we raised, some existing wallets could not be opened. Such a change
            #       can happen to features not-yet-merged-to-BOLTs (e.g. trampoline feature bit was moved and reused).
        else:
            addr.unknown_tags.append((tag, tagdata))

    if verbose:
        print('hex of signature data (32 byte r, 32 byte s): {}'
              .format(hexlify(sigdecoded[0:64])))
        print('recovery flag: {}'.format(sigdecoded[64]))
        data8 = bytes(convertbits(data5, 5, 8, True))
        print('hex of data for signing: {}'
              .format(hexlify(hrp.encode("ascii") + data8)))
        print('SHA256 of above: {}'.format(sha256(hrp.encode("ascii") + data8).hexdigest()))

    # BOLT #11:
    #
    # A reader MUST check that the `signature` is valid (see the `n` tagged
    # field specified below).
    addr.signature = sigdecoded[:65]
    hrp_hash = sha256(hrp.encode("ascii") + bytes(convertbits(data5, 5, 8, True))).digest()
    if addr.pubkey:  # Specified by `n`
        # BOLT #11:
        #
        # 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")
        pubkey_copy = addr.pubkey

        class WrappedBytesKey:
            serialize = lambda: pubkey_copy

        addr.pubkey = WrappedBytesKey
    else: # Recover pubkey from signature.
        addr.pubkey = SerializableKey(ecc.ECPubkey.from_ecdsa_sig64(sigdecoded[:64], sigdecoded[64], hrp_hash))

    return addr
Parses a string into an LnAddr object.
    Can raise LnDecodeException or IncompatibleOrInsaneFeatures.
    
electrum/lnaddr.py