gossipd: handle receipt of modern onion message.

And wire it through to the hook; update the plugins to recognize
modern vs obs2 onions.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell
2021-11-30 13:36:05 +10:30
parent e7b263304e
commit 5361107c9c
8 changed files with 248 additions and 47 deletions

View File

@@ -458,6 +458,7 @@ static u8 *handle_obs2_onion_message(struct peer *peer, const u8 *msg)
towire_tlvstream_raw(&omsg, om->fields);
daemon_conn_send(peer->daemon->master,
take(towire_gossipd_got_onionmsg_to_us(NULL,
true, /* obs2 */
&alias, self_id,
reply_blinding,
first_node_id,
@@ -527,7 +528,148 @@ static struct io_plan *onionmsg_req(struct io_conn *conn, struct daemon *daemon,
/* Peer sends an onion msg. */
static u8 *handle_onion_message(struct peer *peer, const u8 *msg)
{
/* FIXME! */
enum onion_wire badreason;
struct onionpacket *op;
struct pubkey blinding, ephemeral;
struct route_step *rs;
u8 *onion;
struct tlv_onionmsg_payload *om;
struct secret ss, onion_ss;
const u8 *cursor;
size_t max, maxlen;
/* Ignore unless explicitly turned on. */
if (!feature_offered(peer->daemon->our_features->bits[NODE_ANNOUNCE_FEATURE],
OPT_ONION_MESSAGES))
return NULL;
/* FIXME: ratelimit! */
if (!fromwire_onion_message(msg, msg, &blinding, &onion))
return towire_warningfmt(peer, NULL, "Bad onion_message");
/* We unwrap the onion now. */
op = parse_onionpacket(tmpctx, onion, tal_bytelen(onion), &badreason);
if (!op) {
status_peer_debug(&peer->id, "onion msg: can't parse onionpacket: %s",
onion_wire_name(badreason));
return NULL;
}
ephemeral = op->ephemeralkey;
if (!unblind_onion(&blinding, ecdh, &ephemeral, &ss)) {
status_peer_debug(&peer->id, "onion msg: can't unblind onionpacket");
return NULL;
}
/* Now get onion shared secret and parse it. */
ecdh(&ephemeral, &onion_ss);
rs = process_onionpacket(tmpctx, op, &onion_ss, NULL, 0, false);
if (!rs) {
status_peer_debug(&peer->id,
"onion msg: can't process onionpacket ss=%s",
type_to_string(tmpctx, struct secret, &onion_ss));
return NULL;
}
/* The raw payload is prepended with length in the modern onion. */
cursor = rs->raw_payload;
max = tal_bytelen(rs->raw_payload);
maxlen = fromwire_bigsize(&cursor, &max);
if (!cursor) {
status_peer_debug(&peer->id, "onion msg: Invalid hop payload %s",
tal_hex(tmpctx, rs->raw_payload));
return NULL;
}
if (maxlen > max) {
status_peer_debug(&peer->id, "onion msg: overlong hop payload %s",
tal_hex(tmpctx, rs->raw_payload));
return NULL;
}
om = tlv_onionmsg_payload_new(msg);
if (!fromwire_onionmsg_payload(&cursor, &maxlen, om)) {
status_peer_debug(&peer->id, "onion msg: invalid onionmsg_payload %s",
tal_hex(tmpctx, rs->raw_payload));
return NULL;
}
if (rs->nextcase == ONION_END) {
struct pubkey *reply_blinding, *first_node_id, me, alias;
const struct onionmsg_path **reply_path;
struct secret *self_id;
u8 *omsg;
if (!pubkey_from_node_id(&me, &peer->daemon->id)) {
status_broken("Failed to convert own id");
return NULL;
}
/* Final enctlv is actually optional */
if (!om->encrypted_data_tlv) {
alias = me;
self_id = NULL;
} else if (!decrypt_final_enctlv(tmpctx, &blinding, &ss,
om->encrypted_data_tlv, &me, &alias,
&self_id)) {
status_peer_debug(&peer->id,
"onion msg: failed to decrypt enctlv"
" %s", tal_hex(tmpctx, om->encrypted_data_tlv));
return NULL;
}
if (om->reply_path) {
first_node_id = &om->reply_path->first_node_id;
reply_blinding = &om->reply_path->blinding;
reply_path = cast_const2(const struct onionmsg_path **,
om->reply_path->path);
} else {
first_node_id = NULL;
reply_blinding = NULL;
reply_path = NULL;
}
/* We re-marshall here by policy, before handing to lightningd */
omsg = tal_arr(tmpctx, u8, 0);
towire_tlvstream_raw(&omsg, om->fields);
daemon_conn_send(peer->daemon->master,
take(towire_gossipd_got_onionmsg_to_us(NULL,
false, /* !obs2 */
&alias, self_id,
reply_blinding,
first_node_id,
reply_path,
omsg)));
} else {
struct pubkey next_node, next_blinding;
struct peer *next_peer;
struct node_id next_node_id;
/* This fails as expected if no enctlv. */
if (!decrypt_enctlv(&blinding, &ss, om->encrypted_data_tlv, &next_node,
&next_blinding)) {
status_peer_debug(&peer->id,
"onion msg: invalid enctlv %s",
tal_hex(tmpctx, om->encrypted_data_tlv));
return NULL;
}
/* FIXME: Handle short_channel_id! */
node_id_from_pubkey(&next_node_id, &next_node);
next_peer = find_peer(peer->daemon, &next_node_id);
if (!next_peer) {
status_peer_debug(&peer->id,
"onion msg: unknown next peer %s",
type_to_string(tmpctx,
struct pubkey,
&next_node));
return NULL;
}
queue_peer_msg(next_peer,
take(towire_onion_message(NULL,
&next_blinding,
serialize_onionpacket(tmpctx, rs->next))));
}
return NULL;
}

View File

@@ -75,6 +75,7 @@ msgtype,gossipd_new_blockheight,3026
msgdata,gossipd_new_blockheight,blockheight,u32,
msgtype,gossipd_got_onionmsg_to_us,3145
msgdata,gossipd_got_onionmsg_to_us,obs2,bool,
msgdata,gossipd_got_onionmsg_to_us,node_alias,pubkey,
msgdata,gossipd_got_onionmsg_to_us,self_id,?secret,
msgdata,gossipd_got_onionmsg_to_us,reply_blinding,?pubkey,
1 #include <common/cryptomsg.h>
75 msgdata,gossipd_send_onionmsg,blinding,pubkey, msgdata,gossipd_send_onionmsg,onion,u8,onion_len
76 # Lightningd tells us to inject a gossip message (for addgossip RPC) msgdata,gossipd_send_onionmsg,blinding,pubkey,
77 msgtype,gossipd_addgossip,3044 # Lightningd tells us to inject a gossip message (for addgossip RPC)
78 msgtype,gossipd_addgossip,3044
79 msgdata,gossipd_addgossip,len,u16,
80 msgdata,gossipd_addgossip,msg,u8,len
81 # Empty string means no problem.

View File

@@ -21,7 +21,9 @@ struct onion_message_hook_payload {
struct pubkey *reply_first_node;
struct pubkey *our_alias;
struct tlv_obs2_onionmsg_payload *om;
/* Exactly one of these is set! */
struct tlv_onionmsg_payload *om;
struct tlv_obs2_onionmsg_payload *obs2_om;
};
static void json_add_blindedpath(struct json_stream *stream,
@@ -61,28 +63,55 @@ static void onion_message_serialize(struct onion_message_hook_payload *payload,
}
/* Common convenience fields */
if (payload->om->invoice_request)
json_add_hex_talarr(stream, "invoice_request",
payload->om->invoice_request);
if (payload->om->invoice)
json_add_hex_talarr(stream, "invoice", payload->om->invoice);
if (payload->obs2_om) {
json_add_bool(stream, "obs2", true);
if (payload->obs2_om->invoice_request)
json_add_hex_talarr(stream, "invoice_request",
payload->obs2_om->invoice_request);
if (payload->obs2_om->invoice)
json_add_hex_talarr(stream, "invoice", payload->obs2_om->invoice);
if (payload->om->invoice_error)
json_add_hex_talarr(stream, "invoice_error",
payload->om->invoice_error);
if (payload->obs2_om->invoice_error)
json_add_hex_talarr(stream, "invoice_error",
payload->obs2_om->invoice_error);
json_array_start(stream, "unknown_fields");
for (size_t i = 0; i < tal_count(payload->om->fields); i++) {
if (payload->om->fields[i].meta)
continue;
json_object_start(stream, NULL);
json_add_u64(stream, "number", payload->om->fields[i].numtype);
json_add_hex(stream, "value",
payload->om->fields[i].value,
payload->om->fields[i].length);
json_object_end(stream);
json_array_start(stream, "unknown_fields");
for (size_t i = 0; i < tal_count(payload->obs2_om->fields); i++) {
if (payload->obs2_om->fields[i].meta)
continue;
json_object_start(stream, NULL);
json_add_u64(stream, "number", payload->obs2_om->fields[i].numtype);
json_add_hex(stream, "value",
payload->obs2_om->fields[i].value,
payload->obs2_om->fields[i].length);
json_object_end(stream);
}
json_array_end(stream);
} else {
json_add_bool(stream, "obs2", false);
if (payload->om->invoice_request)
json_add_hex_talarr(stream, "invoice_request",
payload->om->invoice_request);
if (payload->om->invoice)
json_add_hex_talarr(stream, "invoice", payload->om->invoice);
if (payload->om->invoice_error)
json_add_hex_talarr(stream, "invoice_error",
payload->om->invoice_error);
json_array_start(stream, "unknown_fields");
for (size_t i = 0; i < tal_count(payload->om->fields); i++) {
if (payload->om->fields[i].meta)
continue;
json_object_start(stream, NULL);
json_add_u64(stream, "number", payload->om->fields[i].numtype);
json_add_hex(stream, "value",
payload->om->fields[i].value,
payload->om->fields[i].length);
json_object_end(stream);
}
json_array_end(stream);
}
json_array_end(stream);
json_object_end(stream);
}
@@ -113,14 +142,15 @@ void handle_onionmsg_to_us(struct lightningd *ld, const u8 *msg)
struct onion_message_hook_payload *payload;
u8 *submsg;
struct secret *self_id;
bool obs2;
size_t submsglen;
const u8 *subptr;
payload = tal(ld, struct onion_message_hook_payload);
payload->om = tlv_obs2_onionmsg_payload_new(payload);
payload->our_alias = tal(payload, struct pubkey);
if (!fromwire_gossipd_got_onionmsg_to_us(payload, msg,
&obs2,
payload->our_alias,
&self_id,
&payload->reply_blinding,
@@ -139,12 +169,25 @@ void handle_onionmsg_to_us(struct lightningd *ld, const u8 *msg)
submsglen = tal_bytelen(submsg);
subptr = submsg;
if (!fromwire_obs2_onionmsg_payload(&subptr,
&submsglen, payload->om)) {
tal_free(payload);
log_broken(ld->log, "bad got_onionmsg_tous om: %s",
tal_hex(tmpctx, msg));
return;
if (obs2) {
payload->om = NULL;
payload->obs2_om = tlv_obs2_onionmsg_payload_new(payload);
if (!fromwire_obs2_onionmsg_payload(&subptr,
&submsglen, payload->obs2_om)) {
tal_free(payload);
log_broken(ld->log, "bad got_onionmsg_tous obs2 om: %s",
tal_hex(tmpctx, msg));
return;
}
} else {
payload->obs2_om = NULL;
payload->om = tlv_onionmsg_payload_new(payload);
if (!fromwire_onionmsg_payload(&subptr, &submsglen, payload->om)) {
tal_free(payload);
log_broken(ld->log, "bad got_onionmsg_tous om: %s",
tal_hex(tmpctx, msg));
return;
}
}
tal_free(submsg);

View File

@@ -140,7 +140,8 @@ static struct command_result *onion_message_modern_call(struct command *cmd,
const jsmntok_t *params)
{
const jsmntok_t *om, *replytok, *invreqtok, *invtok;
struct tlv_obs2_onionmsg_payload_reply_path *reply_path;
struct tlv_obs2_onionmsg_payload_reply_path *obs2_reply_path = NULL;
struct tlv_onionmsg_payload_reply_path *reply_path = NULL;
if (!offers_enabled)
return command_hook_success(cmd);
@@ -148,21 +149,31 @@ static struct command_result *onion_message_modern_call(struct command *cmd,
om = json_get_member(buf, params, "onion_message");
replytok = json_get_member(buf, om, "reply_blindedpath");
if (replytok) {
reply_path = json_to_obs2_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;
bool obs2;
json_to_bool(buf, json_get_member(buf, om, "obs2"), &obs2);
if (obs2) {
obs2_reply_path = json_to_obs2_reply_path(cmd, buf, replytok);
if (!obs2_reply_path)
plugin_err(cmd->plugin, "Invalid obs2 reply path %.*s?",
json_tok_full_len(replytok),
json_tok_full(buf, replytok));
} else {
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));
}
}
invreqtok = json_get_member(buf, om, "invoice_request");
if (invreqtok) {
const u8 *invreqbin = json_tok_bin_from_hex(tmpctx, buf, invreqtok);
if (reply_path)
if (reply_path || obs2_reply_path)
return handle_invoice_request(cmd,
invreqbin,
reply_path);
reply_path,
obs2_reply_path);
else
plugin_log(cmd->plugin, LOG_DBG,
"invoice_request without reply_path");
@@ -172,7 +183,7 @@ static struct command_result *onion_message_modern_call(struct command *cmd,
if (invtok) {
const u8 *invbin = json_tok_bin_from_hex(tmpctx, buf, invtok);
if (invbin)
return handle_invoice(cmd, invbin, reply_path);
return handle_invoice(cmd, invbin, reply_path, obs2_reply_path);
}
return command_hook_success(cmd);

View File

@@ -315,7 +315,8 @@ static struct command_result *listoffers_error(struct command *cmd,
struct command_result *handle_invoice(struct command *cmd,
const u8 *invbin,
struct tlv_obs2_onionmsg_payload_reply_path *reply_path STEALS)
struct tlv_onionmsg_payload_reply_path *reply_path STEALS,
struct tlv_obs2_onionmsg_payload_reply_path *obs2_reply_path STEALS)
{
size_t len = tal_count(invbin);
struct inv *inv = tal(cmd, struct inv);
@@ -324,8 +325,8 @@ struct command_result *handle_invoice(struct command *cmd,
int bad_feature;
struct sha256 m, shash;
inv->obs2_reply_path = tal_steal(inv, reply_path);
inv->reply_path = NULL;
inv->obs2_reply_path = tal_steal(inv, obs2_reply_path);
inv->reply_path = tal_steal(inv, reply_path);
inv->inv = tlv_invoice_new(cmd);
if (!fromwire_invoice(&invbin, &len, inv->inv)) {

View File

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

View File

@@ -833,15 +833,16 @@ static struct command_result *handle_offerless_request(struct command *cmd,
struct command_result *handle_invoice_request(struct command *cmd,
const u8 *invreqbin,
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)
{
size_t len = tal_count(invreqbin);
struct invreq *ir = tal(cmd, struct invreq);
struct out_req *req;
int bad_feature;
ir->obs2_reply_path = tal_steal(ir, reply_path);
ir->reply_path = NULL;
ir->obs2_reply_path = tal_steal(ir, obs2_reply_path);
ir->reply_path = tal_steal(ir, reply_path);
ir->invreq = tlv_invoice_request_new(cmd);
if (!fromwire_invoice_request(&invreqbin, &len, ir->invreq)) {

View File

@@ -8,5 +8,6 @@ extern u32 cltv_final;
/* We got an onionmessage with an invreq! */
struct command_result *handle_invoice_request(struct command *cmd,
const u8 *invreqbin,
struct tlv_obs2_onionmsg_payload_reply_path *reply_path STEALS);
struct tlv_onionmsg_payload_reply_path *reply_path STEALS,
struct tlv_obs2_onionmsg_payload_reply_path *obs2_reply_path STEALS);
#endif /* LIGHTNING_PLUGINS_OFFERS_INVREQ_HOOK_H */