From e7b263304e12eba115e7addc9a4af15a0853c651 Mon Sep 17 00:00:00 2001 From: Rusty Russell 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 --- 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 419ed2ca1..3ac14a19f 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 4a1e49a35..ea1986f4c 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 6b73080dd..73a088b74 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 59eb153b6..829600872 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 4a9f98889..995eb0f71 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 00a5480fb..68feefa12 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 efbaa6f6f..292718271 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 ef37f393e..92ba923c3 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)) {