offers: check they use the blinded path if one is specified.

Now we can specify a blinded path in an offer, we have to check they
used it (or didn't use it, if we didn't have one!).

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2024-07-17 12:53:25 +09:30 committed by Vincenzo Palazzo
parent 27578d5c1d
commit 0d4b9ad6ce
5 changed files with 68 additions and 9 deletions

View File

@ -255,7 +255,7 @@ static struct command_result *onion_message_recv(struct command *cmd,
if (reply_path) if (reply_path)
return handle_invoice_request(cmd, return handle_invoice_request(cmd,
invreqbin, invreqbin,
reply_path); reply_path, secret);
else else
plugin_log(cmd->plugin, LOG_DBG, plugin_log(cmd->plugin, LOG_DBG,
"invoice_request without reply_path"); "invoice_request without reply_path");
@ -265,7 +265,7 @@ static struct command_result *onion_message_recv(struct command *cmd,
if (invtok) { if (invtok) {
const u8 *invbin = json_tok_bin_from_hex(tmpctx, buf, invtok); const u8 *invbin = json_tok_bin_from_hex(tmpctx, buf, invtok);
if (invbin) if (invbin)
return handle_invoice(cmd, invbin, reply_path); return handle_invoice(cmd, invbin, reply_path, secret);
} }
return command_hook_success(cmd); return command_hook_success(cmd);

View File

@ -209,7 +209,8 @@ static struct command_result *listinvreqs_error(struct command *cmd,
struct command_result *handle_invoice(struct command *cmd, struct command_result *handle_invoice(struct command *cmd,
const u8 *invbin, const u8 *invbin,
struct blinded_path *reply_path STEALS) struct blinded_path *reply_path STEALS,
const struct secret *secret)
{ {
size_t len = tal_count(invbin); size_t len = tal_count(invbin);
struct inv *inv = tal(cmd, struct inv); struct inv *inv = tal(cmd, struct inv);
@ -227,6 +228,16 @@ struct command_result *handle_invoice(struct command *cmd,
} }
invoice_invreq_id(inv->inv, &inv->invreq_id); invoice_invreq_id(inv->inv, &inv->invreq_id);
/* We never publish invoice_requests with a reply path, so replies via
* a path are invalid */
if (secret) {
if (command_dev_apis(cmd))
return fail_inv(cmd, inv, "Unexpected blinded path");
/* Normally, "I don't know what you're talking about!" */
return fail_inv(cmd, inv, "Unknown invoice_request %s",
fmt_sha256(tmpctx, &inv->invreq_id));
}
/* BOLT-offers #12: /* BOLT-offers #12:
* A reader of an invoice: * A reader of an invoice:
* - MUST reject the invoice if `invoice_amount` is not present. * - MUST reject the invoice if `invoice_amount` is not present.

View File

@ -6,6 +6,7 @@
/* We got an onionmessage with an invoice! reply_path could be NULL. */ /* We got an onionmessage with an invoice! reply_path could be NULL. */
struct command_result *handle_invoice(struct command *cmd, struct command_result *handle_invoice(struct command *cmd,
const u8 *invbin, const u8 *invbin,
struct blinded_path *reply_path STEALS); struct blinded_path *reply_path STEALS,
const struct secret *secret);
#endif /* LIGHTNING_PLUGINS_OFFERS_INV_HOOK_H */ #endif /* LIGHTNING_PLUGINS_OFFERS_INV_HOOK_H */

View File

@ -2,6 +2,7 @@
#include <bitcoin/chainparams.h> #include <bitcoin/chainparams.h>
#include <bitcoin/preimage.h> #include <bitcoin/preimage.h>
#include <ccan/cast/cast.h> #include <ccan/cast/cast.h>
#include <ccan/mem/mem.h>
#include <ccan/tal/str/str.h> #include <ccan/tal/str/str.h>
#include <common/bech32_util.h> #include <common/bech32_util.h>
#include <common/blindedpath.h> #include <common/blindedpath.h>
@ -35,6 +36,9 @@ struct invreq {
/* The preimage for the invoice. */ /* The preimage for the invoice. */
struct preimage preimage; struct preimage preimage;
/* Optional secret. */
const struct secret *secret;
}; };
static struct command_result *WARN_UNUSED_RESULT static struct command_result *WARN_UNUSED_RESULT
@ -820,7 +824,50 @@ static struct command_result *listoffers_done(struct command *cmd,
if (arr->size == 0) if (arr->size == 0)
return fail_invreq(cmd, ir, "Unknown offer"); return fail_invreq(cmd, ir, "Unknown offer");
/* Now, since we looked up by hash, we know that the entire offer
* is faithfully mirrored in this invreq. */
/* BOLT-offers #4:
*
* The final recipient:
*...
* - MUST ignore the message if the `path_id` does not match
* the blinded route it created
*/
offertok = arr + 1; offertok = arr + 1;
if (ir->secret) {
struct sha256 offer_id;
const u8 *blinding_path_secret;
struct blinded_path **offer_paths;
if (!ir->invreq->offer_paths) {
/* You should not have used a blinded path for invreq */
if (command_dev_apis(cmd))
return fail_invreq(cmd, ir, "Unexpected blinded path");
return fail_invreq(cmd, ir, "Unknown offer");
}
/* We generated this without the paths, so temporarily remove them */
offer_paths = ir->invreq->offer_paths;
ir->invreq->offer_paths = NULL;
invreq_offer_id(ir->invreq, &offer_id);
ir->invreq->offer_paths = offer_paths;
blinding_path_secret = invoice_path_id(tmpctx,
&offerblinding_base, &offer_id);
if (!memeq(ir->secret, tal_bytelen(ir->secret),
blinding_path_secret, tal_bytelen(blinding_path_secret))) {
/* You used the wrong blinded path for invreq */
if (command_dev_apis(cmd))
return fail_invreq(cmd, ir, "Wrong blinded path");
return fail_invreq(cmd, ir, "Unknown offer");
}
} else {
if (ir->invreq->offer_paths) {
/* You should have used a blinded path for invreq */
if (command_dev_apis(cmd))
return fail_invreq(cmd, ir, "Expected blinded path");
return fail_invreq(cmd, ir, "Unknown offer");
}
}
activetok = json_get_member(buf, offertok, "active"); activetok = json_get_member(buf, offertok, "active");
if (!activetok) { if (!activetok) {
@ -833,8 +880,6 @@ static struct command_result *listoffers_done(struct command *cmd,
if (!active) if (!active)
return fail_invreq(cmd, ir, "Offer no longer available"); return fail_invreq(cmd, ir, "Offer no longer available");
/* Now, since we looked up by hash, we know that the entire offer
* is faithfully mirrored in this invreq. */
b12tok = json_get_member(buf, offertok, "bolt12"); b12tok = json_get_member(buf, offertok, "bolt12");
if (!b12tok) { if (!b12tok) {
return fail_internalerr(cmd, ir, return fail_internalerr(cmd, ir,
@ -972,7 +1017,8 @@ static struct command_result *listoffers_done(struct command *cmd,
struct command_result *handle_invoice_request(struct command *cmd, struct command_result *handle_invoice_request(struct command *cmd,
const u8 *invreqbin, const u8 *invreqbin,
struct blinded_path *reply_path) struct blinded_path *reply_path,
const struct secret *secret)
{ {
struct out_req *req; struct out_req *req;
int bad_feature; int bad_feature;
@ -981,7 +1027,7 @@ struct command_result *handle_invoice_request(struct command *cmd,
struct invreq *ir = tal(cmd, struct invreq); struct invreq *ir = tal(cmd, struct invreq);
ir->reply_path = tal_steal(ir, reply_path); ir->reply_path = tal_steal(ir, reply_path);
ir->secret = tal_dup_or_null(ir, struct secret, secret);
ir->invreq = fromwire_tlv_invoice_request(cmd, &cursor, &len); ir->invreq = fromwire_tlv_invoice_request(cmd, &cursor, &len);
/* BOLT-offers #12: /* BOLT-offers #12:

View File

@ -6,5 +6,6 @@
/* We got an onionmessage with an invreq! */ /* We got an onionmessage with an invreq! */
struct command_result *handle_invoice_request(struct command *cmd, struct command_result *handle_invoice_request(struct command *cmd,
const u8 *invreqbin, const u8 *invreqbin,
struct blinded_path *reply_path STEALS); struct blinded_path *reply_path STEALS,
const struct secret *secret TAKES);
#endif /* LIGHTNING_PLUGINS_OFFERS_INVREQ_HOOK_H */ #endif /* LIGHTNING_PLUGINS_OFFERS_INVREQ_HOOK_H */