offers: use amount hints in find_best_peer for blinded paths.
What changed, and why it matters
This change improves how Core Lightning selects peers when building private 'blinded' payment paths for invoices. Before, the code could pick a peer whose channel couldn't actually receive the invoice amount, leading to failed or unreliable payments. Now it checks the required amount against each peer's receiving capacity and skips unsuitable peers. It is a robustness fix, not a direct exploit patch, and the commit message does not frame it as a security issue.
Treat as a normal reliability/robustness improvement. Include in regular release notes; no urgent security advisory is warranted based on the commit alone. If blinded-path failures had operational or privacy implications in deployed versions, consider a low-severity note.
Security signals we found
Defensive correctness fix in payment-path selection
Precludes blinded-path construction through under-capacity peers
No explicit security framing by author or vendor
No memory-safety, cryptographic, or authorization changes observed
Evidence from the diff
The commit extends find_best_peer() in the offers plugin to accept an optional amount_msat capacity hint. When adding blinded paths to invoice requests (offers_invreq_hook.c), it passes the invoice amount so peers whose htlc_max_msat (capped at receivable by listincoming) is below the needed amount are filtered out. For outgoing invoice requests (offers_offer.c), the amount is left NULL with a FIXME noting the minimum outgoing amount should ideally be considered. The change is defensive: it prevents constructing blinded routes through peers that cannot settle the intended payment amount.
Changed components
plugins/offers.cplugins/offers.hplugins/offers_invreq_hook.cplugins/offers_offer.cInspect captured patch +17 / −4
diff --git a/plugins/offers.c b/plugins/offers.c
index 841ebb17..b2e8b722 100644
--- a/plugins/offers.c
+++ b/plugins/offers.c
@@ -320,6 +320,7 @@ struct find_best_peer_data {
const struct chaninfo *,
void *);
u64 needed_features;
+ const struct amount_msat *amount;
const struct pubkey *fronting_nodes;
void *arg;
};
@@ -395,6 +396,11 @@ static struct command_result *listincoming_done(struct command *cmd,
if (!enabled)
continue;
+ /* Make sure it can pay us what we need (misleadingly,
+ * listincoming caps "htlc_max_msat" at "receivable") */
+ if (data->amount && amount_msat_less(ci.htlc_max, *data->amount))
+ continue;
+
/* Not presented if there's no channel_announcement for peer:
* we could use listpeers, but if it's private we probably
* don't want to blinded route through it! */
@@ -425,6 +431,7 @@ static struct command_result *listincoming_done(struct command *cmd,
struct command_result *find_best_peer_(struct command *cmd,
u64 needed_features,
+ const struct amount_msat *amount,
const struct pubkey *fronting_nodes,
struct command_result *(*cb)(struct command *,
const struct chaninfo *,
@@ -435,6 +442,7 @@ struct command_result *find_best_peer_(struct command *cmd,
struct find_best_peer_data *data = tal(cmd, struct find_best_peer_data);
data->cb = cb;
data->arg = arg;
+ data->amount = tal_dup_or_null(data, struct amount_msat, amount);
data->needed_features = needed_features;
if (fronting_nodes)
data->fronting_nodes = tal_dup_talarr(data, struct pubkey, fronting_nodes);
diff --git a/plugins/offers.h b/plugins/offers.h
index 8d2402ac..88f9ceb2 100644
--- a/plugins/offers.h
+++ b/plugins/offers.h
@@ -83,17 +83,18 @@ struct chaninfo {
u32 feebase, feeppm, cltv;
};
-/* Calls listpeerchannels, then cb with best peer (if any!) which has needed_feature */
+/* Calls listpeerchannels, then cb with best peer (if any!) which has needed_feature and (if set) the given payment capacity. */
struct command_result *find_best_peer_(struct command *cmd,
u64 needed_features,
+ const struct amount_msat *amount,
const struct pubkey *fronting_nodes,
struct command_result *(*cb)(struct command *,
const struct chaninfo *,
void *),
void *arg);
-#define find_best_peer(cmd, needed_features, fronting_nodes, cb, arg) \
- find_best_peer_((cmd), (needed_features), (fronting_nodes), \
+#define find_best_peer(cmd, needed_features, amount, fronting_nodes, cb, arg) \
+ find_best_peer_((cmd), (needed_features), (amount), (fronting_nodes), \
typesafe_cb_preargs(struct command_result *, void *, \
(cb), (arg), \
struct command *, \
diff --git a/plugins/offers_invreq_hook.c b/plugins/offers_invreq_hook.c
index 597fcc9a..95614072 100644
--- a/plugins/offers_invreq_hook.c
+++ b/plugins/offers_invreq_hook.c
@@ -396,6 +396,7 @@ static struct command_result *found_best_peer(struct command *cmd,
static struct command_result *add_blindedpaths(struct command *cmd,
struct invreq *ir)
{
+ struct amount_msat amount = amount_msat(*ir->inv->invoice_amount);
if (!we_want_blinded_path(cmd->plugin, ir->fronting_nodes, true))
return create_invoicereq(cmd, ir);
@@ -405,6 +406,7 @@ static struct command_result *add_blindedpaths(struct command *cmd,
* us onion messaging. */
return find_best_peer(cmd,
(1ULL << OPT_ROUTE_BLINDING) | (1ULL << OPT_ONION_MESSAGES),
+ &amount,
ir->fronting_nodes, found_best_peer, ir);
}
diff --git a/plugins/offers_offer.c b/plugins/offers_offer.c
index 0ac50886..dd3a627c 100644
--- a/plugins/offers_offer.c
+++ b/plugins/offers_offer.c
@@ -344,7 +344,7 @@ static struct command_result *maybe_add_path(struct command *cmd,
tal_count(offinfo->fronting_nodes));
} else {
return find_best_peer(cmd, 1ULL << OPT_ONION_MESSAGES,
- NULL,
+ NULL, NULL,
found_best_peer, offinfo);
}
}
@@ -819,6 +819,8 @@ struct command_result *json_invoicerequest(struct command *cmd,
idata->single_use = *single_use;
idata->label = label;
return find_best_peer(cmd, 1ULL << OPT_ONION_MESSAGES,
+ /* FIXME: Strictly speaking, we should specify minimum *outgoing* here! */
+ NULL,
od->fronting_nodes,
found_best_peer_invrequest, idata);
}
Why this scored 27/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.