Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion common/bolt11.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#include <secp256k1_recovery.h>

/* We only have 10 bits for the field length, meaning < 640 bytes */
#define BOLT11_FIELD_BYTE_LIMIT ((1 << 10) * 5 / 8)
#define BOLT11_FIELD_BYTE_LIMIT (((1 << 10) * 5 / 8) - 1)

/* BOLT #11:
* * `c` (24): `data_length` variable.
Expand Down
4 changes: 2 additions & 2 deletions lightningd/invoice.c
Original file line number Diff line number Diff line change
Expand Up @@ -1137,8 +1137,8 @@ static struct command_result *json_invoice(struct command *cmd,

if (strlen(desc_val) > BOLT11_FIELD_BYTE_LIMIT && !*hashonly) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Descriptions greater than %d bytes "
"not yet supported "
"Description greater than %d bytes "
"invalid "
"(description length %zu)",
BOLT11_FIELD_BYTE_LIMIT,
strlen(desc_val));
Expand Down
3 changes: 2 additions & 1 deletion plugins/keysend.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <ccan/asort/asort.h>
#include <ccan/cast/cast.h>
#include <ccan/tal/str/str.h>
#include <common/bolt11.h>
#include <common/gossmap.h>
#include <common/json_param.h>
#include <common/json_stream.h>
Expand Down Expand Up @@ -563,7 +564,7 @@ static struct command_result *htlc_accepted_call(struct command *cmd,
(const char *)desc_field->value);
json_add_string(req->js, "description", desc);
/* Don't exceed max possible desc length! */
if (strlen(desc) > 1023)
if (strlen(desc) > BOLT11_FIELD_BYTE_LIMIT)
json_add_bool(req->js, "deschashonly", true);
} else {
json_add_string(req->js, "description", "keysend");
Expand Down
24 changes: 24 additions & 0 deletions tests/test_invoices.py
Original file line number Diff line number Diff line change
Expand Up @@ -937,3 +937,27 @@ def test_invoice_botched_migration(node_factory, chainparams):
assert ([(i['created_index'], i['label']) for i in l1.rpc.listinvoices()["invoices"]]
== [(1, "made_after_bad_migration"), (2, "label1")])
assert l1.rpc.invoice(100, "test", "test")["created_index"] == 3


def test_invoice_maxdesc(node_factory, chainparams):
l1, l2 = node_factory.line_graph(2)

# BOLT #11:
#
# Note that the maximum length of a Tagged Field's `data` is constricted
# by the maximum value of `data_length`. This is 1023 x 5 bits, or 639
# bytes.
maxdesc = "x" * 639

# This should fail!
with pytest.raises(RpcError, match=r'Description greater than 639 bytes invalid \(description length 641\)'):
l1.rpc.invoice(123000, 'test_invoice_maxdesc', maxdesc + 'xx')

# This should also fail, but used to produce
# lnbcrt1230n1p5dm097sp545trjl795r3mm86mk4ln5jpjvnh04x8aryl3qadjt99vspu646zspp52hf43ln8vg0564ljwccs8d84xc70ls8n7wdmp75ygp7ll8rprqzsdqq0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rcxqyjw5qcqp99qxpqysgqr6l8swzm6jc42ehy4v7s83jrggtwa9ua39cvy46c46tmqwn97mn43ycww7e9cf4w5ws8lxnef2k3m5nfa5c34nz54jaxhzc5e72q0ccq26n9fx
with pytest.raises(RpcError, match=r'Description greater than 639 bytes invalid \(description length 640\)'):
l1.rpc.invoice(123000, 'test_invoice_maxdesc2', maxdesc + 'x')

# This should succeed.
inv = l1.rpc.invoice(123000, 'test_invoice_maxdesc3', maxdesc)
assert l1.rpc.decode(inv['bolt11'])['description'] == maxdesc
25 changes: 25 additions & 0 deletions tests/test_pay.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
tu64_encode
)
import copy
import json
import os
import pytest
import random
Expand Down Expand Up @@ -3697,6 +3698,30 @@ def test_keysend_maxfee(node_factory):
assert len(l3.rpc.listinvoices()['invoices']) == 1


@pytest.mark.parametrize("tlv_payload_length", [638, 639, 640, 641, 1022, 1023, 1024])
def test_keysend_description_size_limit(node_factory, tlv_payload_length):
"""
Test keysend description handling near BOLT11 field size limits.

Checks boundary conditions where the payload length is just below,
exactly at, and just above the maximum allowed tagged-field size.

See common/bolt11.h: BOLT11_FIELD_BYTE_LIMIT.
"""
l1, l2 = node_factory.line_graph(2, wait_for_announce=True)
amt = 10000
prefix = 'keysend: {"message": ""}'
base_len = len(prefix)

# Prep TLV payload with test length
body_len = tlv_payload_length - base_len
tlv_payload = json.dumps({"message": "a" * body_len}).encode().hex()

# Send keysend payment with test payload
l1.rpc.keysend(l2.info["id"], amt, extratlvs={7629169: tlv_payload})
assert len(l2.rpc.listinvoices()["invoices"]) == 1


def test_invalid_onion_channel_update(node_factory):
'''
Some onion failures "should" send a `channel_update`.
Expand Down
Loading