keysend: enforce BOLT11 description length limit
What changed, and why it matters
This commit fixes a bug in Core Lightning's 'keysend' payment feature where invoice descriptions between 641 and 1023 bytes long were incorrectly rejected. The keysend plugin used a hard-coded 1023-byte cutoff, while the rest of the software enforced a stricter 640-byte BOLT11 protocol limit. The patch makes keysend use the same shared limit, so medium-length descriptions now work correctly. This is a reliability/standards-compliance fix rather than a critical security vulnerability.
No urgent action required. Users and operators running keysend payments with descriptions between 641 and 1023 bytes should upgrade to benefit from the fix. Reviewers may want to audit other plugins for similar hard-coded BOLT11 limit mismatches.
Security signals we found
Inconsistent validation between plugin and core protocol limit
Boundary condition fix with regression tests for lengths near the limit
Use of shared protocol constant instead of magic number
Evidence from the diff
The keysend plugin’s htlc_accepted_call handler previously compared description length to a literal 1023 bytes when deciding whether to set deschashonly. The rest of Core Lightning (invoice.c and common/bolt11.h) enforces BOLT11_FIELD_BYTE_LIMIT, which is 640 bytes. The mismatch meant descriptions of 641–1023 bytes caused invoice insertion to fail unexpectedly. The patch changes the comparison to strlen(desc) >= BOLT11_FIELD_BYTE_LIMIT and adds a regression test covering lengths 638–1024 bytes.
Changed components
plugins/keysend.ctests/test_pay.pyInspect captured patch +26 / −1
diff --git a/plugins/keysend.c b/plugins/keysend.c
index 6fdc4f18..5a7071e3 100644
--- a/plugins/keysend.c
+++ b/plugins/keysend.c
@@ -561,7 +561,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");
diff --git a/tests/test_pay.py b/tests/test_pay.py
index 3bfc2899..442e35d7 100644
--- a/tests/test_pay.py
+++ b/tests/test_pay.py
@@ -11,6 +11,7 @@ from utils import (
tu64_encode
)
import copy
+import json
import os
import pytest
import random
@@ -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`.
Why this scored 35/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.