common: enforce currency-requires-amount check.
What changed, and why it matters
This change makes Core Lightning refuse BOLT12 offers that specify a currency but no amount. Previously the code did not enforce this rule, which could let a node process malformed offers in ways the protocol says it must not. The fix is small and adds a test that now passes.
Treat as a low-to-moderate reliability/protocol-compliance fix. Review whether any other BOLT12 mandatory offer field combinations are unenforced, and ensure the new test covers edge cases such as currency with a zero versus missing amount.
Security signals we found
Protocol compliance enforcement (BOLT #12 currency-requires-amount rule)
Rejection of malformed offer input that was previously accepted
Removal of xfail test marker indicating a known-failing behavior is now fixed
Evidence from the diff
In common/bolt12.c, offer_decode() now checks whether offer_currency is set while offer_amount is unset. If so, it rejects the offer with the failure message “Offer contains a currency with no amount” and frees the decoded offer. The accompanying test test_offer_currency_no_amount had been marked as expected to fail (xfail); that marker is removed because the test now passes.
Changed components
common/bolt12.ctests/test_pay.pyInspect captured patch +10 / −1
diff --git a/common/bolt12.c b/common/bolt12.c
index 8335dbff..4621e62f 100644
--- a/common/bolt12.c
+++ b/common/bolt12.c
@@ -222,6 +222,16 @@ struct tlv_offer *offer_decode(const tal_t *ctx,
return tal_free(offer);
}
+ /* BOLT #12:
+ *
+ * - if `offer_currency` is set and `offer_amount` is not set:
+ * - MUST NOT respond to the offer.
+ */
+ if (offer->offer_currency && !offer->offer_amount) {
+ *fail = tal_strdup(ctx, "Offer contains a currency with no amount");
+ return tal_free(offer);
+ }
+
/* BOLT #12:
*
* - if neither `offer_issuer_id` nor `offer_paths` are set:
diff --git a/tests/test_pay.py b/tests/test_pay.py
index 8fb2a83c..215050bc 100644
--- a/tests/test_pay.py
+++ b/tests/test_pay.py
@@ -7172,7 +7172,6 @@ def test_invoice_amount_override(node_factory):
assert l1.rpc.waitsendpay(inv["payment_hash"])["status"] == "complete"
-@pytest.mark.xfail(strict=True)
def test_offer_currency_no_amount(node_factory):
l1 = node_factory.get_node()
ret = l1.rpc.decode("lno1qcp4256ypgx9getnwss8vetrw3hhyuckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg")
Why this scored 44/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.