lightningd: add override amt to invoice_check_payment
What changed, and why it matters
This commit adds a new optional override to the internal invoice payment-checking function. It lets the node accept a payment whose amount differs from what the invoice originally stated. The change is purely internal infrastructure: all existing callers pass NULL, so the override is not yet used. There is no direct security bug visible in the diff, but it changes the assumptions around invoice amounts, which could matter for future callers.
Review the callers that will use expected_msat_override once they are added, to ensure the override value is derived safely and cannot be manipulated by a payer to underpay or overpay invoices. Verify that the override is only set by trusted internal logic (e.g., BOLT12 offer amount differences) and not from untrusted RPC or network input.
Security signals we found
Changes payment-amount validation logic in invoice acceptance path
Introduces an override that can bypass the invoice-stated amount
All current callers pass NULL, so behavior is unchanged for now
No input sanitization or bounds checks beyond existing twice-amount ceiling are visible for the override value
No vendor security framing or CVE references present
Evidence from the diff
The commit modifies invoice_check_payment() in lightningd/invoice.c to accept an optional expected_msat_override pointer. If non-NULL, that value replaces *details->msat when validating that the offered payment amount is neither below the expected amount nor more than double it. Callers in htlc_set.c, pay.c, and the wallet test stub are updated to pass NULL, preserving existing behavior. The change enables future code paths to charge a different amount than the invoice specifies.
Changed components
lightningd/invoice.clightningd/invoice.hlightningd/htlc_set.clightningd/pay.cwallet/test/run-wallet.cInspect captured patch +14 / −6
diff --git a/lightningd/htlc_set.c b/lightningd/htlc_set.c
index 5d2531ff..b2fbf86e 100644
--- a/lightningd/htlc_set.c
+++ b/lightningd/htlc_set.c
@@ -150,8 +150,8 @@ void htlc_set_add_(struct lightningd *ld,
* [Failure Messages](#failure-messages)
* - Note: "amount paid" specified there is the `total_msat` field.
*/
- details = invoice_check_payment(tmpctx, ld, payment_hash,
- total_msat, payment_secret, &err);
+ details = invoice_check_payment(tmpctx, ld, payment_hash, total_msat,
+ NULL, payment_secret, &err);
if (!details) {
log_debug(log, "payment failed: %s", err);
fail(arg, take(failmsg_incorrect_or_unknown(NULL, ld, msat)));
diff --git a/lightningd/invoice.c b/lightningd/invoice.c
index dfdf633e..198a58e1 100644
--- a/lightningd/invoice.c
+++ b/lightningd/invoice.c
@@ -332,6 +332,7 @@ invoice_check_payment(const tal_t *ctx,
struct lightningd *ld,
const struct sha256 *payment_hash,
const struct amount_msat msat,
+ const struct amount_msat *expected_msat_override,
const struct secret *payment_secret,
const char **err)
{
@@ -408,15 +409,19 @@ invoice_check_payment(const tal_t *ctx,
if (details->msat != NULL) {
struct amount_msat twice;
- if (amount_msat_less(msat, *details->msat)) {
+ /* Override the expected amount. */
+ struct amount_msat expected_msat =
+ expected_msat_override ? *expected_msat_override : *details->msat;
+
+ if (amount_msat_less(msat, expected_msat)) {
*err = tal_fmt(ctx, "Attempt to pay %s with amount %s < %s",
fmt_sha256(tmpctx, &details->rhash),
fmt_amount_msat(tmpctx, msat),
- fmt_amount_msat(tmpctx, *details->msat));
+ fmt_amount_msat(tmpctx, expected_msat));
return tal_free(details);
}
- if (amount_msat_add(&twice, *details->msat, *details->msat)
+ if (amount_msat_add(&twice, expected_msat, expected_msat)
&& amount_msat_greater(msat, twice)) {
*err = tal_fmt(ctx, "Attempt to pay %s with amount %s > %s",
fmt_sha256(tmpctx, &details->rhash),
diff --git a/lightningd/invoice.h b/lightningd/invoice.h
index 43e98b2a..c7b24e11 100644
--- a/lightningd/invoice.h
+++ b/lightningd/invoice.h
@@ -50,6 +50,7 @@ struct invoice_details {
* @ld: lightningd
* @payment_hash: hash of preimage they want.
* @msat: amount they offer to pay.
+ * @expected_msat_override: if set: overrides the amount we expect to be payed.
* @payment_secret: they payment secret they sent, if any.
* @err: error string if it returns NULL.
*
@@ -59,6 +60,7 @@ const struct invoice_details *invoice_check_payment(const tal_t *ctx,
struct lightningd *ld,
const struct sha256 *payment_hash,
const struct amount_msat msat,
+ const struct amount_msat *expected_msat_override,
const struct secret *payment_secret,
const char **err);
diff --git a/lightningd/pay.c b/lightningd/pay.c
index a4151994..10432eca 100644
--- a/lightningd/pay.c
+++ b/lightningd/pay.c
@@ -1482,7 +1482,7 @@ static struct command_result *self_payment(struct lightningd *ld,
local_invreq_id);
/* Now, resolve the invoice */
- inv = invoice_check_payment(tmpctx, ld, rhash, msat, payment_secret, &err);
+ inv = invoice_check_payment(tmpctx, ld, rhash, msat, NULL, payment_secret, &err);
if (!inv) {
struct routing_failure *fail;
wallet_payment_set_status(ld->wallet, rhash, partid, groupid,
diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c
index 2c5454b3..3ca09e02 100644
--- a/wallet/test/run-wallet.c
+++ b/wallet/test/run-wallet.c
@@ -391,6 +391,7 @@ const struct invoice_details *invoice_check_payment(const tal_t *ctx UNNEEDED,
struct lightningd *ld UNNEEDED,
const struct sha256 *payment_hash UNNEEDED,
const struct amount_msat msat UNNEEDED,
+ const struct amount_msat *expected_msat_override UNNEEDED,
const struct secret *payment_secret UNNEEDED,
const char **err UNNEEDED)
{ fprintf(stderr, "invoice_check_payment called!\n"); abort(); }
Why this scored 28/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.