xpay: correctly identify final node when using a dummy blinded path.
What changed, and why it matters
This is a small bug-fix in Core Lightning's experimental 'xpay' payment plugin. When a payment used a privacy feature called a 'blinded path' (a dummy route that hides the real final recipient), the plugin could misidentify which node reported a payment failure. The actual payment behavior was unchanged, but the internal reasoning used to decide what to do next was misleading. There is no direct security exploit here; it is a correctness fix that prevents a wrong diagnostic from potentially causing a poor routing decision later.
Treat as a routine correctness fix. Review whether other failure-handling paths in xpay make similar assumptions about hop index versus destination identity when blinded paths are present. No urgent security response is indicated by the diff or commit message alone.
Security signals we found
Logic error in failure attribution for privacy-preserving payment paths
Potential for incorrect routing knowledge updates from misclassified onion errors
No input validation, memory safety, or cryptographic flaw visible in diff
Evidence from the diff
In plugins/xpay/xpay.c, update_knowledge_from_error() previously set from_final only when the failing hop index equaled the total hop count. With blinded paths, the real destination node can appear earlier in the route, so a failure reported by that node was treated as coming from an intermediate hop. The patch adds a second condition: if the previous hop’s next_node public key equals the payment destination, from_final is also true. This makes failure classification consistent for invoices that include dummy blinded paths. The commit message explicitly states the practical result was the same (the blinded path would be removed), but the classification was misleading.
Changed components
plugins/xpay/xpay.cupdate_knowledge_from_error()Blinded-path payment handlingInspect captured patch +8 / −1
diff --git a/plugins/xpay/xpay.c b/plugins/xpay/xpay.c
index 4a96b42d..70e1c26a 100644
--- a/plugins/xpay/xpay.c
+++ b/plugins/xpay/xpay.c
@@ -850,7 +850,14 @@ static void update_knowledge_from_error(struct command *aux_cmd,
send_payment_req(aux_cmd, attempt->payment, req);
}
- from_final = (index == tal_count(attempt->hops));
+ /* Because we might include blinded paths, final node is end of route, OR destination node id */
+ if (index == tal_count(attempt->hops)) {
+ from_final = true;
+ } else if (index > 0 && pubkey_eq(&attempt->hops[index-1].next_node,
+ &attempt->payment->destination)) {
+ from_final = true;
+ } else
+ from_final = false;
failcode = fromwire_peektype(replymsg);
failcode_name = onion_wire_name(failcode);
if (strstarts(failcode_name, "WIRE_"))
Why this scored 22/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.