From 368fc07d055d6db7e1a8dd39a0776db43c3b79b2 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 1 Oct 2021 06:49:37 +0930 Subject: [PATCH] offers: send a modern onion reply in response to a modern request. Signed-off-by: Rusty Russell Changelog-Experimental: Protocol: Updated onion_message support to match updated draft specification (with backwards compat for old version) --- plugins/offers.c | 105 ++++++++++++++++++++++++++++++++++- plugins/offers.h | 3 + plugins/offers_inv_hook.c | 24 +++++--- plugins/offers_inv_hook.h | 5 +- plugins/offers_invreq_hook.c | 25 +++++++-- plugins/offers_invreq_hook.h | 5 +- 6 files changed, 148 insertions(+), 19 deletions(-) diff --git a/plugins/offers.c b/plugins/offers.c index ef8b7d3b1..906ae07e8 100644 --- a/plugins/offers.c +++ b/plugins/offers.c @@ -1,6 +1,7 @@ /* This plugin covers both sending and receiving offers */ #include #include +#include #include #include #include @@ -37,8 +38,53 @@ static struct command_result *sendonionmessage_error(struct command *cmd, return command_hook_success(cmd); } +/* FIXME: replyfield string interface is to accomodate obsolete API */ +static struct command_result * +send_modern_onion_reply(struct command *cmd, + struct tlv_onionmsg_payload_reply_path *reply_path, + const char *replyfield, + const u8 *replydata) +{ + struct out_req *req; + size_t nhops = tal_count(reply_path->path); + + req = jsonrpc_request_start(cmd->plugin, cmd, "sendonionmessage", + finished, sendonionmessage_error, NULL); + + json_add_pubkey(req->js, "first_id", &reply_path->first_node_id); + json_add_pubkey(req->js, "blinding", &reply_path->blinding); + json_array_start(req->js, "hops"); + for (size_t i = 0; i < nhops; i++) { + struct tlv_onionmsg_payload *omp; + u8 *tlv; + + json_object_start(req->js, NULL); + json_add_pubkey(req->js, "id", &reply_path->path[i]->node_id); + + omp = tlv_onionmsg_payload_new(tmpctx); + omp->enctlv = reply_path->path[i]->enctlv; + + /* Put payload in last hop. */ + if (i == nhops - 1) { + if (streq(replyfield, "invoice")) { + omp->invoice = cast_const(u8 *, replydata); + } else { + assert(streq(replyfield, "invoice_error")); + omp->invoice_error = cast_const(u8 *, replydata); + } + } + tlv = tal_arr(tmpctx, u8, 0); + towire_onionmsg_payload(&tlv, omp); + json_add_hex_talarr(req->js, "tlv", tlv); + json_object_end(req->js); + } + json_array_end(req->js); + return send_outreq(cmd->plugin, req); +} + struct command_result *WARN_UNUSED_RESULT send_onion_reply(struct command *cmd, + struct tlv_onionmsg_payload_reply_path *reply_path, const char *jsonbuf, const jsmntok_t *replytok, const char *replyfield, @@ -48,7 +94,11 @@ send_onion_reply(struct command *cmd, size_t i; const jsmntok_t *t; - plugin_log(cmd->plugin, LOG_DBG, "sending reply %s = %s", + if (reply_path) + return send_modern_onion_reply(cmd, reply_path, + replyfield, replydata); + + plugin_log(cmd->plugin, LOG_DBG, "sending obs reply %s = %s", replyfield, tal_hex(tmpctx, replydata)); /* Send to requester, using return route. */ @@ -97,7 +147,7 @@ static struct command_result *onion_message_call(struct command *cmd, replytok = json_get_member(buf, om, "reply_path"); if (replytok && replytok->size > 0) return handle_invoice_request(cmd, buf, - invreqtok, replytok); + invreqtok, replytok, NULL); else plugin_log(cmd->plugin, LOG_DBG, "invoice_request without reply_path"); @@ -108,7 +158,52 @@ static struct command_result *onion_message_call(struct command *cmd, const jsmntok_t *replytok; replytok = json_get_member(buf, om, "reply_path"); - return handle_invoice(cmd, buf, invtok, replytok); + return handle_invoice(cmd, buf, invtok, replytok, NULL); + } + + return command_hook_success(cmd); +} + +static struct command_result *onion_message_modern_call(struct command *cmd, + const char *buf, + const jsmntok_t *params) +{ + const jsmntok_t *om, *replytok, *invreqtok, *invtok; + bool obsolete; + struct tlv_onionmsg_payload_reply_path *reply_path; + + if (!offers_enabled) + return command_hook_success(cmd); + + om = json_get_member(buf, params, "onion_message"); + json_to_bool(buf, json_get_member(buf, om, "obsolete"), &obsolete); + if (obsolete) + return command_hook_success(cmd); + + replytok = json_get_member(buf, om, "reply_blindedpath"); + if (replytok) { + reply_path = json_to_reply_path(cmd, buf, replytok); + if (!reply_path) + plugin_err(cmd->plugin, "Invalid reply path %.*s?", + json_tok_full_len(replytok), + json_tok_full(buf, replytok)); + } else + reply_path = NULL; + + invreqtok = json_get_member(buf, om, "invoice_request"); + if (invreqtok) { + if (reply_path) + return handle_invoice_request(cmd, buf, + invreqtok, + NULL, reply_path); + else + plugin_log(cmd->plugin, LOG_DBG, + "invoice_request without reply_path"); + } + + invtok = json_get_member(buf, om, "invoice"); + if (invtok) { + return handle_invoice(cmd, buf, invtok, NULL, reply_path); } return command_hook_success(cmd); @@ -119,6 +214,10 @@ static const struct plugin_hook hooks[] = { "onion_message", onion_message_call }, + { + "onion_message_blinded", + onion_message_modern_call + }, }; struct decodable { diff --git a/plugins/offers.h b/plugins/offers.h index bc9783972..2c58df73d 100644 --- a/plugins/offers.h +++ b/plugins/offers.h @@ -9,6 +9,9 @@ struct command; /* Helper to send a reply */ struct command_result *WARN_UNUSED_RESULT send_onion_reply(struct command *cmd, + /* Preferred */ + struct tlv_onionmsg_payload_reply_path *reply_path, + /* Used if reply_path is NULL */ const char *jsonbuf, const jsmntok_t *replytok, const char *replyfield, diff --git a/plugins/offers_inv_hook.c b/plugins/offers_inv_hook.c index 8d5ebfbd9..ece5e1179 100644 --- a/plugins/offers_inv_hook.c +++ b/plugins/offers_inv_hook.c @@ -13,6 +13,7 @@ struct inv { const char *buf; /* May be NULL */ const jsmntok_t *replytok; + struct tlv_onionmsg_payload_reply_path *reply_path; /* The offer, once we've looked it up. */ struct tlv_offer *offer; @@ -40,7 +41,7 @@ fail_inv_level(struct command *cmd, plugin_log(cmd->plugin, l, "%s", msg); /* Only reply if they gave us a path */ - if (!inv->replytok) + if (!inv->replytok && !inv->reply_path) return command_hook_success(cmd); /* Don't send back internal error details. */ @@ -54,7 +55,8 @@ fail_inv_level(struct command *cmd, errdata = tal_arr(cmd, u8, 0); towire_invoice_error(&errdata, err); - return send_onion_reply(cmd, inv->buf, inv->replytok, "invoice_error", errdata); + return send_onion_reply(cmd, inv->reply_path, inv->buf, inv->replytok, + "invoice_error", errdata); } static struct command_result *WARN_UNUSED_RESULT @@ -315,7 +317,8 @@ static struct command_result *listoffers_error(struct command *cmd, struct command_result *handle_invoice(struct command *cmd, const char *buf, const jsmntok_t *invtok, - const jsmntok_t *replytok) + const jsmntok_t *replytok, + struct tlv_onionmsg_payload_reply_path *reply_path) { const u8 *invbin = json_tok_bin_from_hex(cmd, buf, invtok); size_t len = tal_count(invbin); @@ -325,10 +328,17 @@ struct command_result *handle_invoice(struct command *cmd, int bad_feature; struct sha256 m, shash; - /* Make a copy of entire buffer, for later. */ - inv->buf = tal_dup_arr(inv, char, buf, replytok->end, 0); - inv->replytok = tal_dup_arr(inv, jsmntok_t, replytok, - json_next(replytok) - replytok, 0); + if (reply_path) { + inv->buf = NULL; + inv->replytok = NULL; + inv->reply_path = reply_path; + } else { + /* Make a copy of entire buffer, for later. */ + inv->buf = tal_dup_arr(inv, char, buf, replytok->end, 0); + inv->replytok = tal_dup_arr(inv, jsmntok_t, replytok, + json_next(replytok) - replytok, 0); + inv->reply_path = NULL; + } inv->inv = tlv_invoice_new(cmd); if (!fromwire_invoice(&invbin, &len, inv->inv)) { diff --git a/plugins/offers_inv_hook.h b/plugins/offers_inv_hook.h index a5d15d5bf..4e862d30a 100644 --- a/plugins/offers_inv_hook.h +++ b/plugins/offers_inv_hook.h @@ -3,9 +3,10 @@ #include "config.h" #include -/* We got an onionmessage with an invoice! replytok could be NULL. */ +/* We got an onionmessage with an invoice! replytok/reply_path could be NULL. */ struct command_result *handle_invoice(struct command *cmd, const char *buf, const jsmntok_t *invtok, - const jsmntok_t *replytok); + const jsmntok_t *replytok, + struct tlv_onionmsg_payload_reply_path *reply_path); #endif /* LIGHTNING_PLUGINS_OFFERS_INV_HOOK_H */ diff --git a/plugins/offers_invreq_hook.c b/plugins/offers_invreq_hook.c index 8d27f2edc..b622c5b94 100644 --- a/plugins/offers_invreq_hook.c +++ b/plugins/offers_invreq_hook.c @@ -16,7 +16,10 @@ struct invreq { struct tlv_invoice_request *invreq; const char *buf; + /* If obsolete style */ const jsmntok_t *replytok; + /* If modern style. */ + struct tlv_onionmsg_payload_reply_path *reply_path; /* The offer, once we've looked it up. */ struct tlv_offer *offer; @@ -60,7 +63,8 @@ fail_invreq_level(struct command *cmd, errdata = tal_arr(cmd, u8, 0); towire_invoice_error(&errdata, err); - return send_onion_reply(cmd, invreq->buf, invreq->replytok, + return send_onion_reply(cmd, invreq->reply_path, + invreq->buf, invreq->replytok, "invoice_error", errdata); } @@ -180,7 +184,8 @@ static struct command_result *createinvoice_done(struct command *cmd, json_tok_full(buf, t)); } - return send_onion_reply(cmd, ir->buf, ir->replytok, "invoice", rawinv); + return send_onion_reply(cmd, ir->reply_path, ir->buf, ir->replytok, + "invoice", rawinv); } static struct command_result *createinvoice_error(struct command *cmd, @@ -828,7 +833,8 @@ static struct command_result *handle_offerless_request(struct command *cmd, struct command_result *handle_invoice_request(struct command *cmd, const char *buf, const jsmntok_t *invreqtok, - const jsmntok_t *replytok) + const jsmntok_t *replytok, + struct tlv_onionmsg_payload_reply_path *reply_path) { const u8 *invreqbin = json_tok_bin_from_hex(cmd, buf, invreqtok); size_t len = tal_count(invreqbin); @@ -837,9 +843,16 @@ struct command_result *handle_invoice_request(struct command *cmd, int bad_feature; /* Make a copy of entire buffer, for later. */ - ir->buf = tal_dup_arr(ir, char, buf, replytok->end, 0); - ir->replytok = tal_dup_arr(ir, jsmntok_t, replytok, - json_next(replytok) - replytok, 0); + if (reply_path) { + ir->buf = NULL; + ir->replytok = NULL; + ir->reply_path = reply_path; + } else { + ir->buf = tal_dup_arr(ir, char, buf, replytok->end, 0); + ir->replytok = tal_dup_arr(ir, jsmntok_t, replytok, + json_next(replytok) - replytok, 0); + ir->reply_path = NULL; + } ir->invreq = tlv_invoice_request_new(cmd); if (!fromwire_invoice_request(&invreqbin, &len, ir->invreq)) { diff --git a/plugins/offers_invreq_hook.h b/plugins/offers_invreq_hook.h index a3a7965a3..0ea8f023b 100644 --- a/plugins/offers_invreq_hook.h +++ b/plugins/offers_invreq_hook.h @@ -9,5 +9,8 @@ extern u32 cltv_final; struct command_result *handle_invoice_request(struct command *cmd, const char *buf, const jsmntok_t *invreqtok, - const jsmntok_t *replytok); + /* Obsolete onion */ + const jsmntok_t *replytok, + /* Modern onion */ + struct tlv_onionmsg_payload_reply_path *reply_path); #endif /* LIGHTNING_PLUGINS_OFFERS_INVREQ_HOOK_H */