From e7b263304e12eba115e7addc9a4af15a0853c651 Mon Sep 17 00:00:00 2001
From: Rusty Russell <rusty@rustcorp.com.au>
Date: Tue, 30 Nov 2021 13:36:05 +1030
Subject: [PATCH] lightningd: Send updated onion spec messages.

It's very similar to the previous, but there are a few changes:

1. The enctlv fields are numbered differently.
2. The message itself is a different number.

The onionmsg_path type is the same, however, so we keep that constant
at least.

The result is a lot of cut & paste, but we will delete the old one
next release.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
---
 gossipd/gossipd.c            |  12 ++--
 gossipd/gossipd_wire.csv     |   1 +
 lightningd/onion_message.c   |  35 ++++++++++--
 plugins/fetchinvoice.c       | 108 ++++++++++++++++++++++++++++++++---
 plugins/offers.c             |  63 ++++++++++++++++++--
 plugins/offers.h             |   3 +-
 plugins/offers_inv_hook.c    |  11 ++--
 plugins/offers_invreq_hook.c |  12 ++--
 8 files changed, 213 insertions(+), 32 deletions(-)

diff --git a/gossipd/gossipd.c b/gossipd/gossipd.c
index 419ed2ca14bf..3ac14a19f7ab 100644
--- a/gossipd/gossipd.c
+++ b/gossipd/gossipd.c
@@ -505,17 +505,21 @@ static struct io_plan *onionmsg_req(struct io_conn *conn, struct daemon *daemon,
 	u8 *onionmsg;
 	struct pubkey blinding;
 	struct peer *peer;
+	bool obs2;
 
-	if (!fromwire_gossipd_send_onionmsg(msg, msg, &id, &onionmsg, &blinding))
+	if (!fromwire_gossipd_send_onionmsg(msg, msg, &obs2, &id, &onionmsg, &blinding))
 		master_badmsg(WIRE_GOSSIPD_SEND_ONIONMSG, msg);
 
 	/* Even though lightningd checks for valid ids, there's a race
 	 * where it might vanish before we read this command. */
 	peer = find_peer(daemon, &id);
 	if (peer) {
-		queue_peer_msg(peer,
-			       take(towire_obs2_onion_message(NULL,
-							      &blinding, onionmsg)));
+		u8 *omsg;
+		if (obs2)
+			omsg = towire_obs2_onion_message(NULL, &blinding, onionmsg);
+		else
+			omsg = towire_onion_message(NULL, &blinding, onionmsg);
+		queue_peer_msg(peer, take(omsg));
 	}
 	return daemon_conn_read_next(conn, daemon->master);
 }
diff --git a/gossipd/gossipd_wire.csv b/gossipd/gossipd_wire.csv
index 4a1e49a35b0a..ea1986f4cc95 100644
--- a/gossipd/gossipd_wire.csv
+++ b/gossipd/gossipd_wire.csv
@@ -86,6 +86,7 @@ msgdata,gossipd_got_onionmsg_to_us,rawmsg,u8,rawmsg_len
 
 # Lightningd tells us to send an onion message.
 msgtype,gossipd_send_onionmsg,3041
+msgdata,gossipd_send_onionmsg,obs2,bool,
 msgdata,gossipd_send_onionmsg,id,node_id,
 msgdata,gossipd_send_onionmsg,onion_len,u16,
 msgdata,gossipd_send_onionmsg,onion,u8,onion_len
diff --git a/lightningd/onion_message.c b/lightningd/onion_message.c
index 6b73080dd324..73a088b7411a 100644
--- a/lightningd/onion_message.c
+++ b/lightningd/onion_message.c
@@ -199,10 +199,11 @@ static struct command_result *param_onion_hops(struct command *cmd,
 	return NULL;
 }
 
-static struct command_result *json_sendonionmessage(struct command *cmd,
-						    const char *buffer,
-						    const jsmntok_t *obj UNNEEDED,
-						    const jsmntok_t *params)
+static struct command_result *json_sendonionmessage2(struct command *cmd,
+						     const char *buffer,
+						     const jsmntok_t *obj UNNEEDED,
+						     const jsmntok_t *params,
+						     bool obs2)
 {
 	struct onion_hop *hops;
 	struct node_id *first_id;
@@ -248,13 +249,29 @@ static struct command_result *json_sendonionmessage(struct command *cmd,
 				    "Creating onion failed (tlvs too long?)");
 
 	subd_send_msg(cmd->ld->gossip,
-		      take(towire_gossipd_send_onionmsg(NULL, first_id,
+		      take(towire_gossipd_send_onionmsg(NULL, obs2, first_id,
 					serialize_onionpacket(tmpctx, op),
 					blinding)));
 
 	return command_success(cmd, json_stream_success(cmd));
 }
 
+static struct command_result *json_sendonionmessage(struct command *cmd,
+						    const char *buffer,
+						    const jsmntok_t *obj,
+						    const jsmntok_t *params)
+{
+	return json_sendonionmessage2(cmd, buffer, obj, params, false);
+}
+
+static struct command_result *json_sendobs2onionmessage(struct command *cmd,
+							const char *buffer,
+							const jsmntok_t *obj,
+							const jsmntok_t *params)
+{
+	return json_sendonionmessage2(cmd, buffer, obj, params, true);
+}
+
 static const struct json_command sendonionmessage_command = {
 	"sendonionmessage",
 	"utility",
@@ -263,6 +280,14 @@ static const struct json_command sendonionmessage_command = {
 };
 AUTODATA(json_command, &sendonionmessage_command);
 
+static const struct json_command sendobs2onionmessage_command = {
+	"sendobs2onionmessage",
+	"utility",
+	json_sendobs2onionmessage,
+	"Send obsolete message to {first_id}, using {blinding}, encoded over {hops} (id, tlv)"
+};
+AUTODATA(json_command, &sendobs2onionmessage_command);
+
 static struct command_result *param_pubkeys(struct command *cmd,
 					    const char *name,
 					    const char *buffer,
diff --git a/plugins/fetchinvoice.c b/plugins/fetchinvoice.c
index 59eb153b6360..8296008729da 100644
--- a/plugins/fetchinvoice.c
+++ b/plugins/fetchinvoice.c
@@ -631,6 +631,7 @@ struct sending {
 	struct sent *sent;
 	const char *msgfield;
 	const u8 *msgval;
+	struct tlv_obs2_onionmsg_payload_reply_path *obs2_reply_path;
 	struct command_result *(*done)(struct command *cmd,
 				       const char *buf UNUSED,
 				       const jsmntok_t *result UNUSED,
@@ -638,9 +639,10 @@ struct sending {
 };
 
 static struct command_result *
-send_modern_message(struct command *cmd,
-		    struct tlv_obs2_onionmsg_payload_reply_path *reply_path,
-		    struct sending *sending)
+send_obs2_message(struct command *cmd,
+		  const char *buf,
+		  const jsmntok_t *result,
+		  struct sending *sending)
 {
 	struct sent *sent = sending->sent;
 	struct privkey blinding_iter;
@@ -695,9 +697,9 @@ send_modern_message(struct command *cmd,
 		payloads[nhops-1]->invoice
 			= cast_const(u8 *, sending->msgval);
 	}
-	payloads[nhops-1]->reply_path = reply_path;
+	payloads[nhops-1]->reply_path = sending->obs2_reply_path;
 
-	req = jsonrpc_request_start(cmd->plugin, cmd, "sendonionmessage",
+	req = jsonrpc_request_start(cmd->plugin, cmd, "sendobs2onionmessage",
 				    sending->done,
 				    forward_error,
 				    sending->sent);
@@ -717,6 +719,87 @@ send_modern_message(struct command *cmd,
 	return send_outreq(cmd->plugin, req);
 }
 
+static struct command_result *
+send_modern_message(struct command *cmd,
+		    struct tlv_onionmsg_payload_reply_path *reply_path,
+		    struct sending *sending)
+{
+	struct sent *sent = sending->sent;
+	struct privkey blinding_iter;
+	struct pubkey fwd_blinding, *node_alias;
+	size_t nhops = tal_count(sent->path);
+	struct tlv_onionmsg_payload **payloads;
+	struct out_req *req;
+
+	/* Now create enctlvs for *forward* path. */
+	randombytes_buf(&blinding_iter, sizeof(blinding_iter));
+	if (!pubkey_from_privkey(&blinding_iter, &fwd_blinding))
+		return command_fail(cmd, LIGHTNINGD,
+				    "Could not convert blinding %s to pubkey!",
+				    type_to_string(tmpctx, struct privkey,
+						   &blinding_iter));
+
+	/* We overallocate: this node (0) doesn't have payload or alias */
+	payloads = tal_arr(cmd, struct tlv_onionmsg_payload *, nhops);
+	node_alias = tal_arr(cmd, struct pubkey, nhops);
+
+	for (size_t i = 1; i < nhops - 1; i++) {
+		payloads[i] = tlv_onionmsg_payload_new(payloads);
+		payloads[i]->encrypted_data_tlv = create_enctlv(payloads[i],
+						    &blinding_iter,
+						    &sent->path[i],
+						    &sent->path[i+1],
+						    /* FIXME: Pad? */
+						    0,
+						    NULL,
+						    &blinding_iter,
+						    &node_alias[i]);
+	}
+	/* Final payload contains the actual data. */
+	payloads[nhops-1] = tlv_onionmsg_payload_new(payloads);
+
+	/* We don't include enctlv in final, but it gives us final alias */
+	if (!create_final_enctlv(tmpctx, &blinding_iter, &sent->path[nhops-1],
+				 /* FIXME: Pad? */ 0,
+				 NULL,
+				 &node_alias[nhops-1])) {
+		/* Should not happen! */
+		return command_fail(cmd, LIGHTNINGD,
+				    "Could create final enctlv");
+	}
+
+	/* FIXME: This interface is a string for sendobsonionmessage! */
+	if (streq(sending->msgfield, "invoice_request")) {
+		payloads[nhops-1]->invoice_request
+			= cast_const(u8 *, sending->msgval);
+	} else {
+		assert(streq(sending->msgfield, "invoice"));
+		payloads[nhops-1]->invoice
+			= cast_const(u8 *, sending->msgval);
+	}
+	payloads[nhops-1]->reply_path = reply_path;
+
+	req = jsonrpc_request_start(cmd->plugin, cmd, "sendonionmessage",
+				    /* Try sending older version next */
+				    send_obs2_message,
+				    forward_error,
+				    sending);
+	json_add_pubkey(req->js, "first_id", &sent->path[1]);
+	json_add_pubkey(req->js, "blinding", &fwd_blinding);
+	json_array_start(req->js, "hops");
+	for (size_t i = 1; i < nhops; i++) {
+		u8 *tlv;
+		json_object_start(req->js, NULL);
+		json_add_pubkey(req->js, "id", &node_alias[i]);
+		tlv = tal_arr(tmpctx, u8, 0);
+		towire_onionmsg_payload(&tlv, payloads[i]);
+		json_add_hex_talarr(req->js, "tlv", tlv);
+		json_object_end(req->js);
+	}
+	json_array_end(req->js);
+	return send_outreq(cmd->plugin, req);
+}
+
 /* Lightningd gives us reply path, since we don't know secret to put
  * in final so it will recognize it. */
 static struct command_result *use_reply_path(struct command *cmd,
@@ -724,16 +807,25 @@ static struct command_result *use_reply_path(struct command *cmd,
 					     const jsmntok_t *result,
 					     struct sending *sending)
 {
-	struct tlv_obs2_onionmsg_payload_reply_path *rpath;
+	struct tlv_onionmsg_payload_reply_path *rpath;
 
-	rpath = json_to_obs2_reply_path(cmd, buf,
-					json_get_member(buf, result, "obs2blindedpath"));
+	rpath = json_to_reply_path(cmd, buf,
+				   json_get_member(buf, result, "blindedpath"));
 	if (!rpath)
 		plugin_err(cmd->plugin,
 			   "could not parse reply path %.*s?",
 			   json_tok_full_len(result),
 			   json_tok_full(buf, result));
 
+	sending->obs2_reply_path = json_to_obs2_reply_path(cmd, buf,
+							   json_get_member(buf, result,
+									   "obs2blindedpath"));
+	if (!sending->obs2_reply_path)
+		plugin_err(cmd->plugin,
+			   "could not parse obs2 reply path %.*s?",
+			   json_tok_full_len(result),
+			   json_tok_full(buf, result));
+
 	/* Remember our alias we used so we can recognize reply */
 	sending->sent->reply_alias
 		= tal_dup(sending->sent, struct pubkey,
diff --git a/plugins/offers.c b/plugins/offers.c
index 4a9f9888977c..995eb0f716e2 100644
--- a/plugins/offers.c
+++ b/plugins/offers.c
@@ -41,16 +41,16 @@ static struct command_result *sendonionmessage_error(struct command *cmd,
 }
 
 /* FIXME: replyfield string interface is to accomodate obsolete API */
-struct command_result *
-send_onion_reply(struct command *cmd,
-		 struct tlv_obs2_onionmsg_payload_reply_path *reply_path,
-		 const char *replyfield,
-		 const u8 *replydata)
+static struct command_result *
+send_obs2_onion_reply(struct command *cmd,
+		      struct tlv_obs2_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",
+	req = jsonrpc_request_start(cmd->plugin, cmd, "sendobs2onionmessage",
 				    finished, sendonionmessage_error, NULL);
 
 	json_add_pubkey(req->js, "first_id", &reply_path->first_node_id);
@@ -84,6 +84,57 @@ send_onion_reply(struct command *cmd,
 	return send_outreq(cmd->plugin, req);
 }
 
+struct command_result *
+send_onion_reply(struct command *cmd,
+		 struct tlv_onionmsg_payload_reply_path *reply_path,
+		 struct tlv_obs2_onionmsg_payload_reply_path *obs2_reply_path,
+		 const char *replyfield,
+		 const u8 *replydata)
+{
+	struct out_req *req;
+	size_t nhops;
+
+	/* Exactly one must be set! */
+	assert(!reply_path != !obs2_reply_path);
+	if (obs2_reply_path)
+		return send_obs2_onion_reply(cmd, obs2_reply_path, replyfield, replydata);
+
+	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");
+
+	nhops = tal_count(reply_path->path);
+	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->encrypted_data_tlv = reply_path->path[i]->encrypted_recipient_data;
+
+		/* 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);
+}
+
 static struct command_result *onion_message_modern_call(struct command *cmd,
 							const char *buf,
 							const jsmntok_t *params)
diff --git a/plugins/offers.h b/plugins/offers.h
index 00a5480fb77d..68feefa12d69 100644
--- a/plugins/offers.h
+++ b/plugins/offers.h
@@ -9,7 +9,8 @@ struct command;
 /* Helper to send a reply */
 struct command_result *WARN_UNUSED_RESULT
 send_onion_reply(struct command *cmd,
-		 struct tlv_obs2_onionmsg_payload_reply_path *reply_path,
+		 struct tlv_onionmsg_payload_reply_path *reply_path,
+		 struct tlv_obs2_onionmsg_payload_reply_path *obs2_reply_path,
 		 const char *replyfield,
 		 const u8 *replydata);
 #endif /* LIGHTNING_PLUGINS_OFFERS_H */
diff --git a/plugins/offers_inv_hook.c b/plugins/offers_inv_hook.c
index efbaa6f6fdad..29271827115f 100644
--- a/plugins/offers_inv_hook.c
+++ b/plugins/offers_inv_hook.c
@@ -11,7 +11,8 @@ struct inv {
 	struct tlv_invoice *inv;
 
 	/* May be NULL */
-	struct tlv_obs2_onionmsg_payload_reply_path *reply_path;
+	struct tlv_obs2_onionmsg_payload_reply_path *obs2_reply_path;
+	struct tlv_onionmsg_payload_reply_path *reply_path;
 
 	/* The offer, once we've looked it up. */
 	struct tlv_offer *offer;
@@ -39,7 +40,7 @@ fail_inv_level(struct command *cmd,
 	plugin_log(cmd->plugin, l, "%s", msg);
 
 	/* Only reply if they gave us a path */
-	if (!inv->reply_path)
+	if (!inv->reply_path && !inv->obs2_reply_path)
 		return command_hook_success(cmd);
 
 	/* Don't send back internal error details. */
@@ -53,7 +54,8 @@ fail_inv_level(struct command *cmd,
 
 	errdata = tal_arr(cmd, u8, 0);
 	towire_invoice_error(&errdata, err);
-	return send_onion_reply(cmd, inv->reply_path, "invoice_error", errdata);
+	return send_onion_reply(cmd, inv->reply_path, inv->obs2_reply_path,
+				"invoice_error", errdata);
 }
 
 static struct command_result *WARN_UNUSED_RESULT
@@ -322,7 +324,8 @@ struct command_result *handle_invoice(struct command *cmd,
 	int bad_feature;
 	struct sha256 m, shash;
 
-	inv->reply_path = tal_steal(inv, reply_path);
+	inv->obs2_reply_path = tal_steal(inv, reply_path);
+	inv->reply_path = NULL;
 
 	inv->inv = tlv_invoice_new(cmd);
 	if (!fromwire_invoice(&invbin, &len, inv->inv)) {
diff --git a/plugins/offers_invreq_hook.c b/plugins/offers_invreq_hook.c
index ef37f393e0c0..92ba923c3110 100644
--- a/plugins/offers_invreq_hook.c
+++ b/plugins/offers_invreq_hook.c
@@ -15,7 +15,8 @@
 /* We need to keep the reply path around so we can reply with invoice */
 struct invreq {
 	struct tlv_invoice_request *invreq;
-	struct tlv_obs2_onionmsg_payload_reply_path *reply_path;
+	struct tlv_onionmsg_payload_reply_path *reply_path;
+	struct tlv_obs2_onionmsg_payload_reply_path *obs2_reply_path;
 
 	/* The offer, once we've looked it up. */
 	struct tlv_offer *offer;
@@ -59,7 +60,8 @@ fail_invreq_level(struct command *cmd,
 
 	errdata = tal_arr(cmd, u8, 0);
 	towire_invoice_error(&errdata, err);
-	return send_onion_reply(cmd, invreq->reply_path, "invoice_error", errdata);
+	return send_onion_reply(cmd, invreq->reply_path, invreq->obs2_reply_path,
+				"invoice_error", errdata);
 }
 
 static struct command_result *WARN_UNUSED_RESULT PRINTF_FMT(3,4)
@@ -178,7 +180,8 @@ static struct command_result *createinvoice_done(struct command *cmd,
 					json_tok_full(buf, t));
 	}
 
-	return send_onion_reply(cmd, ir->reply_path, "invoice", rawinv);
+	return send_onion_reply(cmd, ir->reply_path, ir->obs2_reply_path,
+				"invoice", rawinv);
 }
 
 static struct command_result *createinvoice_error(struct command *cmd,
@@ -837,7 +840,8 @@ struct command_result *handle_invoice_request(struct command *cmd,
 	struct out_req *req;
 	int bad_feature;
 
-	ir->reply_path = tal_steal(ir, reply_path);
+	ir->obs2_reply_path = tal_steal(ir, reply_path);
+	ir->reply_path = NULL;
 
 	ir->invreq = tlv_invoice_request_new(cmd);
 	if (!fromwire_invoice_request(&invreqbin, &len, ir->invreq)) {