Repository navigation
Description
decode raises ValueError, ReadError or UnicodeDecodeError instead of a Bolt11Exception subclass on an x, c or f field with data_length 0, truncated data or a non-UTF-8 d field. This happens on main at 51bd3b97, the v2.2.0 tag, and in release 2.1.1.
The test at tests/test_decode_exceptions.py builds the first two inputs with the example key and embeds the last three:
import pytest
from bitstring import Bits
from bolt11 import Bolt11, Fallback, Tags, decode, encode
from bolt11.exceptions import Bolt11Exception
# BOLT 11 example private key, from 11-payment-encoding.md.
PRIVATE_KEY = "e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734"
def _signed_with(extra):
"""A minimal invoice signed with the example key, plus one extra field."""
invoice = Bolt11(
currency="bc",
amount_msat=250_000_000,
date=1496314658,
tags=Tags.from_dict(
{
"payment_hash": (
"0001020304050607080900010203040506070809000102030405060708090102"
),
"payment_secret": (
"1111111111111111111111111111111111111111111111111111111111111111"
),
"description": "1 cup coffee",
**extra,
}
),
)
return encode(invoice, PRIVATE_KEY)
# encode writes data_length 0 for an expiry of 0 and for an empty fallback.
X_DATA_LENGTH_0 = _signed_with({"expire_time": 0})
F_DATA_LENGTH_0 = _signed_with({"fallback": Fallback(Bits(), "bc")})
# Byte layouts encode cannot produce, embedded as literals.
SIGNATURE_ALONE = (
"lntb9678785340p1qqqqqqq0edrv5l46sr6mcupz93w7kjlmujdwp4ufc42p6l7x5ru2j87"
"hvr85362rdqn23h24tyk8c9x8hsuu8jhezu26wwqlvz55dcdqxwkr6p"
)
D_LENGTH_PAST_END = (
"lnbc2500u1pvjluezdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6nat07l32sgac4"
"tnrndh8d44qlp6uv7mv8x4sxt87pn05a4medx6s6dpfcydezlus22seyghffflu4hgpd9tc"
"n30zmy9xgyl97j00mhcpwsnm6a"
)
D_NOT_UTF8 = (
"lnbc1961qqqqqqqdq2udhkven9v5utw390d9t34c43q7sktxf4k37jzau5yl6aqnqha9pg3"
"yvhjrzlg5nu2nxmyrc6eh9t4lanjq6sgng8sjxqhmy2u4tkwdn62u2jp8vjqqkq70wr"
)
class TestDecodeExceptions:
"""
A reader MUST fail malformed invoices with a Bolt11Exception, as decode does
for a bad checksum or a short signature.
https://github.com/lightning/bolts/blob/master/11-payment-encoding.md#requirements-3
"""
@pytest.mark.parametrize(
"invoice",
[
X_DATA_LENGTH_0,
F_DATA_LENGTH_0,
SIGNATURE_ALONE,
D_LENGTH_PAST_END,
D_NOT_UTF8,
],
ids=[
"x-data-length-0",
"f-data-length-0",
"signature-alone",
"d-length-past-end",
"d-not-utf8",
],
)
def test_decode_raises_bolt11_exception(self, invoice):
with pytest.raises(Bolt11Exception):
decode(invoice)The command python -m pytest tests/test_decode_exceptions.py fails every case:
collected 5 items
tests/test_decode_exceptions.py FFFFF
___ test_decode_raises_bolt11_exception[x-data-length-0] ___
bolt11/decode.py:120: in decode
tagdata.uint,
E ValueError: Cannot interpret a zero length bitstring as an integer.
___ test_decode_raises_bolt11_exception[f-data-length-0] ___
bolt11/decode.py:132: in decode
Fallback.from_bitstring(tagdata, currency),
bolt11/models/fallback.py:29: in from_bitstring
wver = data[0:5].uint
E ValueError: Cannot interpret a zero length bitstring as an integer.
___ test_decode_raises_bolt11_exception[signature-alone] ___
bolt11/decode.py:53: in decode
timestamp = data_part.read(35).uint
E bitstring.exceptions.ReadError: Cannot read 35 bits, only 0 available.
___ test_decode_raises_bolt11_exception[d-length-past-end] ___
bolt11/decode.py:26: in _pull_tagged
return (CHARSET[tag], stream.read(length * 5), stream)
E bitstring.exceptions.ReadError: Cannot read 315 bits, only 190 available.
___ test_decode_raises_bolt11_exception[d-not-utf8] ___
bolt11/decode.py:110: in decode
trim_to_bytes(tagdata).decode(),
E UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe3 in position 0: invalid continuation byte
5 failed
An x, c or f field with data_length 0 raises bitstring's ValueError at decode.py#L117-L128 and fallback.py#L29. A data part too short for the 35-bit timestamp, or a field whose data_length runs past the end of the data, raises bitstring.exceptions.ReadError at decode.py#L53 and decode.py#L23-L26. A d field that is not valid UTF-8 raises UnicodeDecodeError at decode.py#L110, and callers that catch Bolt11Exception miss all three.
Each input above should raise a Bolt11Exception, as decode does for a bad checksum or a short signature.
Activity