offers: honor `payment-fronting-nodes` when creating invoice_requests.
What changed, and why it matters
This change fixes a configuration being ignored when creating invoice requests. Core Lightning has a 'payment-fronting-nodes' setting that tells a node to route incoming invoice requests through specific neighbor nodes for privacy. Before this patch, the setting was honored for regular offers but ignored when creating invoice requests, meaning the node could expose its real identity even when it explicitly asked to hide behind a fronting node. The patch makes invoice-request creation respect the setting and fails if no suitable fronting node can be found.
Treat as a low-to-moderate privacy fix. Users relying on payment-fronting-nodes for invoice-request privacy should upgrade. No immediate incident response is indicated by the commit alone.
Security signals we found
Privacy configuration was not applied to invoice-request creation, potentially exposing node identity
New failure path prevents silent fallback to non-fronted invoice requests when fronting is explicitly requested
Test added to enforce that fronting nodes are used for invoice requests
Evidence from the diff
In plugins/offers_offer.c, json_invoicerequest() now passes od->fronting_nodes to find_best_peer() when a blinded path is desired, instead of passing NULL. The found_best_peer_invrequest() callback now fails the command if fronting_nodes was configured but no best peer was found, preventing a fallback to a bare, non-fronted invoice request. A test in tests/test_invoices.py verifies that an invoice request created by a node with payment-fronting-nodes uses the configured fronting node as the first hop in the blinded path.
Changed components
plugins/offers_offer.cBOLT 12 invoice request creationpayment-fronting-nodes configurationInspect captured patch +17 / −3
diff --git a/plugins/offers_offer.c b/plugins/offers_offer.c
index 797da61a..15c1c227 100644
--- a/plugins/offers_offer.c
+++ b/plugins/offers_offer.c
@@ -646,6 +646,12 @@ static struct command_result *found_best_peer_invrequest(struct command *cmd,
const struct offers_data *od = get_offers_data(cmd->plugin);
if (!best) {
+ /* Don't allow bare invoices if they explicitly told us to front */
+ if (od->fronting_nodes) {
+ return command_fail(cmd, LIGHTNINGD,
+ "Could not find neighbour fronting node");
+ }
+
/* FIXME: Make this a warning in the result! */
plugin_log(cmd->plugin, LOG_UNUSUAL,
"No incoming channel to public peer, so no blinded path for invoice request");
@@ -769,15 +775,14 @@ struct command_result *json_invoicerequest(struct command *cmd,
* - MUST set `invreq_features`.`features` to the bitmap of features.
*/
- /* FIXME: We only set blinded path if private/noaddr, we should allow
- * setting otherwise! */
if (we_want_blinded_path(cmd->plugin, false)) {
struct invrequest_data *idata = tal(cmd, struct invrequest_data);
idata->invreq = invreq;
idata->single_use = *single_use;
idata->label = label;
return find_best_peer(cmd, 1ULL << OPT_ONION_MESSAGES,
- NULL, found_best_peer_invrequest, idata);
+ od->fronting_nodes,
+ found_best_peer_invrequest, idata);
}
return call_createinvoicerequest(cmd, invreq, *single_use, label);
diff --git a/tests/test_invoices.py b/tests/test_invoices.py
index 65fadd6c..12654b7f 100644
--- a/tests/test_invoices.py
+++ b/tests/test_invoices.py
@@ -986,6 +986,15 @@ def test_payment_fronting(node_factory):
l1.rpc.xpay(l3invb12)
l1.rpc.xpay(l4invb12)
+ # Balance so l3 can pay ->l1->l4.
+ l3inv2 = l3.rpc.invoice(10000000, 'l3inv2', 'l3inv2')['bolt11']
+ l1.rpc.xpay(l3inv2)
+
+ # When l3 creates an invoice request, it will also use the fronting nodes.
+ l3invreq = l3.rpc.invoicerequest(amount=1000, description='l3invreq')['bolt12']
+ assert only_one(l3.rpc.decode(l3invreq)['invreq_paths'])['first_node_id'] == l1.info['id']
+ l4.rpc.sendinvoice(invreq=l3invreq, label='l3invreq')
+
def test_invoice_maxdesc(node_factory, chainparams):
l1, l2 = node_factory.line_graph(2)
Why this scored 42/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.