From d0605ac6325c221011e72262671cf9e351f7c47a Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 1 Apr 2020 10:21:03 +1030 Subject: [PATCH 01/11] sendpay: do route parsing inside parameter parsing. This makes "check" more accurate, and also simplifies the code a little. Signed-off-by: Rusty Russell --- lightningd/pay.c | 92 +++++++++++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 41 deletions(-) diff --git a/lightningd/pay.c b/lightningd/pay.c index 28980e038a0a..cce8c641f68d 100644 --- a/lightningd/pay.c +++ b/lightningd/pay.c @@ -1195,38 +1195,21 @@ static struct command_result *param_route_hop_style(struct command *cmd, json_tok_full(buffer, tok)); } -static struct command_result *json_sendpay(struct command *cmd, - const char *buffer, - const jsmntok_t *obj UNNEEDED, - const jsmntok_t *params) +static struct command_result *param_route_hops(struct command *cmd, + const char *name, + const char *buffer, + const jsmntok_t *tok, + struct route_hop **hops) { - const jsmntok_t *routetok; - const jsmntok_t *t; size_t i; - struct sha256 *rhash; - struct route_hop *route; - struct amount_msat *msat; - const char *b11str, *label; - u64 *partid; - struct secret *payment_secret; - - /* For generating help, give new-style. */ - if (!param(cmd, buffer, params, - p_req("route", param_array, &routetok), - p_req("payment_hash", param_sha256, &rhash), - p_opt("label", param_escaped_string, &label), - p_opt("msatoshi", param_msat, &msat), - p_opt("bolt11", param_string, &b11str), - p_opt("payment_secret", param_secret, &payment_secret), - p_opt_def("partid", param_u64, &partid, 0), - NULL)) - return command_param_failed(); + const jsmntok_t *t; - if (routetok->size == 0) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Empty route"); + if (tok->type != JSMN_ARRAY || tok->size == 0) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "%s must be an (non-empty) array", name); - route = tal_arr(cmd, struct route_hop, routetok->size); - json_for_each_arr(i, t, routetok) { + *hops = tal_arr(cmd, struct route_hop, tok->size); + json_for_each_arr(i, t, tok) { struct amount_msat *msat, *amount_msat; struct node_id *id; struct short_channel_id *channel; @@ -1249,16 +1232,16 @@ static struct command_result *json_sendpay(struct command *cmd, if (!msat && !amount_msat) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "route[%zi]: must have msatoshi" - " or amount_msat", i); + "%s[%zi]: must have msatoshi" + " or amount_msat", name, i); if (!id || !channel || !delay) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "route[%zi]: must have id, channel" - " and delay", i); + "%s[%zi]: must have id, channel" + " and delay", name, i); if (msat && amount_msat && !amount_msat_eq(*msat, *amount_msat)) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "route[%zi]: msatoshi %s != amount_msat %s", - i, + "%s[%zi]: msatoshi %s != amount_msat %s", + name, i, type_to_string(tmpctx, struct amount_msat, msat), @@ -1268,20 +1251,47 @@ static struct command_result *json_sendpay(struct command *cmd, if (!msat) msat = amount_msat; - route[i].amount = *msat; - route[i].nodeid = *id; - route[i].delay = *delay; - route[i].channel_id = *channel; - route[i].style = *style; + (*hops)[i].amount = *msat; + (*hops)[i].nodeid = *id; + (*hops)[i].delay = *delay; + (*hops)[i].channel_id = *channel; + (*hops)[i].style = *style; /* FIXME: Actually ignored by sending code! */ - route[i].direction = direction ? *direction : 0; + (*hops)[i].direction = direction ? *direction : 0; } + return NULL; +} + +static struct command_result *json_sendpay(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) +{ + struct sha256 *rhash; + struct route_hop *route; + struct amount_msat *msat; + const char *b11str, *label; + u64 *partid; + struct secret *payment_secret; + + /* For generating help, give new-style. */ + if (!param(cmd, buffer, params, + p_req("route", param_route_hops, &route), + p_req("payment_hash", param_sha256, &rhash), + p_opt("label", param_escaped_string, &label), + p_opt("msatoshi", param_msat, &msat), + p_opt("bolt11", param_string, &b11str), + p_opt("payment_secret", param_secret, &payment_secret), + p_opt_def("partid", param_u64, &partid, 0), + NULL)) + return command_param_failed(); + if (*partid && !msat) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Must specify msatoshi with partid"); - const struct amount_msat final_amount = route[routetok->size-1].amount; + const struct amount_msat final_amount = route[tal_count(route)-1].amount; if (msat && !*partid && !amount_msat_eq(*msat, final_amount)) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, From bd2f2ecbdbea5053f184c7a491f0a8be340d8304 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 1 Apr 2020 10:22:03 +1030 Subject: [PATCH 02/11] EXPERMENTAL_FEATURES: Import onion message types. This tracks https://github.com/lightningnetwork/lightning-rfc/pull/759 Signed-off-by: Rusty Russell --- channeld/channeld.c | 4 +++ gossipd/gossipd.c | 3 ++ tools/check-spelling.sh | 2 +- ...l_1b1c9a71038bd72453cabefd3ace17ce8b8dd96e | 28 +++++++++++++++++++ ...l_1b1c9a71038bd72453cabefd3ace17ce8b8dd96e | 11 ++++++++ wire/peer_wire.c | 6 ++++ wire/wire.h | 14 ++++++++++ 7 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 wire/extracted_onion_experimental_1b1c9a71038bd72453cabefd3ace17ce8b8dd96e create mode 100644 wire/extracted_peer_experimental_1b1c9a71038bd72453cabefd3ace17ce8b8dd96e diff --git a/channeld/channeld.c b/channeld/channeld.c index c30786a419ca..e3cba8f45070 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -1692,6 +1692,10 @@ static void peer_in(struct peer *peer, const u8 *msg) case WIRE_SHUTDOWN: handle_peer_shutdown(peer, msg); return; +#if EXPERIMENTAL_FEATURES + case WIRE_ONION_MESSAGE: + break; +#endif case WIRE_INIT: case WIRE_OPEN_CHANNEL: diff --git a/gossipd/gossipd.c b/gossipd/gossipd.c index dddb19f0b120..73d55187de21 100644 --- a/gossipd/gossipd.c +++ b/gossipd/gossipd.c @@ -488,6 +488,9 @@ static struct io_plan *peer_msg_in(struct io_conn *conn, case WIRE_CHANNEL_REESTABLISH: case WIRE_ANNOUNCEMENT_SIGNATURES: case WIRE_GOSSIP_TIMESTAMP_FILTER: +#if EXPERIMENTAL_FEATURES + case WIRE_ONION_MESSAGE: +#endif status_broken("peer %s: relayed unexpected msg of type %s", type_to_string(tmpctx, struct node_id, &peer->id), wire_type_name(fromwire_peektype(msg))); diff --git a/tools/check-spelling.sh b/tools/check-spelling.sh index a9f88697eeb0..d82a77b2e47c 100755 --- a/tools/check-spelling.sh +++ b/tools/check-spelling.sh @@ -6,7 +6,7 @@ if git --no-pager grep -nHiE 'l[ightn]{6}g|l[ightn]{8}g|ilghtning|lgihtning|lihg exit 1 fi -if git --no-pager grep -nHiE 'ctlv' -- . ':!tools/check-spelling.sh'; then +if git --no-pager grep -nHiE 'ctlv' | grep -v 'enctlv' -- . ':!tools/check-spelling.sh'; then echo "It's check lock time verify, not check time lock verify!" >&2 exit 1 fi diff --git a/wire/extracted_onion_experimental_1b1c9a71038bd72453cabefd3ace17ce8b8dd96e b/wire/extracted_onion_experimental_1b1c9a71038bd72453cabefd3ace17ce8b8dd96e new file mode 100644 index 000000000000..3b8ebbec89a1 --- /dev/null +++ b/wire/extracted_onion_experimental_1b1c9a71038bd72453cabefd3ace17ce8b8dd96e @@ -0,0 +1,28 @@ +--- wire/extracted_onion_wire_csv 2020-03-25 10:24:12.861645774 +1030 ++++ - 2020-03-26 13:47:13.498294435 +1030 +@@ -8,6 +8,25 @@ + tlvtype,tlv_payload,payment_data,8 + tlvdata,tlv_payload,payment_data,payment_secret,byte,32 + tlvdata,tlv_payload,payment_data,total_msat,tu64, ++tlvtype,onionmsg_payload,next_node_id,4 ++tlvdata,onionmsg_payload,next_node_id,node_id,point, ++tlvtype,onionmsg_payload,next_short_channel_id,6 ++tlvdata,onionmsg_payload,next_short_channel_id,short_channel_id,short_channel_id, ++tlvtype,onionmsg_payload,reply_path,8 ++tlvdata,onionmsg_payload,reply_path,blinding,point, ++tlvdata,onionmsg_payload,reply_path,path,onionmsg_path,... ++tlvtype,onionmsg_payload,enctlv,10 ++tlvdata,onionmsg_payload,enctlv,enctlv,byte,... ++tlvtype,onionmsg_payload,blinding,12 ++tlvdata,onionmsg_payload,blinding,blinding,point, ++tlvtype,encmsg_tlvs,next_node_id,4 ++tlvdata,encmsg_tlvs,next_node_id,node_id,point, ++tlvtype,encmsg_tlvs,next_short_channel_id,6 ++tlvdata,encmsg_tlvs,next_short_channel_id,short_channel_id,short_channel_id, ++subtype,onionmsg_path ++subtypedata,onionmsg_path,node_id,point, ++subtypedata,onionmsg_path,enclen,u16, ++subtypedata,onionmsg_path,enctlv,byte,enclen + msgtype,invalid_realm,PERM|1 + msgtype,temporary_node_failure,NODE|2 + msgtype,permanent_node_failure,PERM|NODE|2 diff --git a/wire/extracted_peer_experimental_1b1c9a71038bd72453cabefd3ace17ce8b8dd96e b/wire/extracted_peer_experimental_1b1c9a71038bd72453cabefd3ace17ce8b8dd96e new file mode 100644 index 000000000000..2cb91f2f9691 --- /dev/null +++ b/wire/extracted_peer_experimental_1b1c9a71038bd72453cabefd3ace17ce8b8dd96e @@ -0,0 +1,11 @@ +--- wire/extracted_peer_wire_csv 2020-03-11 10:30:35.744376417 +1030 ++++ - 2020-03-26 13:47:13.409755567 +1030 +@@ -211,3 +211,8 @@ + msgdata,gossip_timestamp_filter,chain_hash,chain_hash, + msgdata,gossip_timestamp_filter,first_timestamp,u32, + msgdata,gossip_timestamp_filter,timestamp_range,u32, ++msgtype,onion_message,385,option_onion_messages ++msgdata,onion_message,onionmsg,byte,1366 ++msgdata,onion_message,onion_message_tlvs,onion_message_tlvs, ++tlvtype,onion_message_tlvs,blinding,2 ++tlvdata,onion_message_tlvs,blinding,blinding,point, diff --git a/wire/peer_wire.c b/wire/peer_wire.c index e9210d7d8434..737e2fbb6caa 100644 --- a/wire/peer_wire.c +++ b/wire/peer_wire.c @@ -31,6 +31,9 @@ static bool unknown_type(enum wire_type t) case WIRE_QUERY_CHANNEL_RANGE: case WIRE_REPLY_CHANNEL_RANGE: case WIRE_GOSSIP_TIMESTAMP_FILTER: +#if EXPERIMENTAL_FEATURES + case WIRE_ONION_MESSAGE: +#endif return false; } return true; @@ -68,6 +71,9 @@ bool is_msg_for_gossipd(const u8 *cursor) case WIRE_CHANNEL_REESTABLISH: case WIRE_ANNOUNCEMENT_SIGNATURES: case WIRE_GOSSIP_TIMESTAMP_FILTER: +#if EXPERIMENTAL_FEATURES + case WIRE_ONION_MESSAGE: +#endif break; } return false; diff --git a/wire/wire.h b/wire/wire.h index ea380a937419..a9bc8e89d9d7 100644 --- a/wire/wire.h +++ b/wire/wire.h @@ -153,4 +153,18 @@ struct witscript *fromwire_witscript(const tal_t *ctx, void fromwire_chainparams(const u8 **cursor, size_t *max, const struct chainparams **chainparams); +#if !EXPERIMENTAL_FEATURES +/* Stubs, as this subtype is only defined when EXPERIMENTAL_FEATURES */ +struct onionmsg_path; + +static inline void towire_onionmsg_path(u8 **p, const struct onionmsg_path *onionmsg_path) +{ +} + +static inline struct onionmsg_path * +fromwire_onionmsg_path(const tal_t *ctx, const u8 **cursor, size_t *plen) +{ + return NULL; +} +#endif /* EXPERIMENTAL_FEATURES */ #endif /* LIGHTNING_WIRE_WIRE_H */ From fcdcb5e92b5c8e0326337ef8b4645ceae7282378 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 1 Apr 2020 14:23:09 +1030 Subject: [PATCH 03/11] channeld: handle onion messages. We do most of the decoding here, and just hand the results to lightningd. Signed-off-by: Rusty Russell --- channeld/channel_wire.csv | 17 ++++ channeld/channeld.c | 146 ++++++++++++++++++++++++++++++++++- lightningd/channel_control.c | 7 +- tools/generate-wire.py | 1 + 4 files changed, 169 insertions(+), 2 deletions(-) diff --git a/channeld/channel_wire.csv b/channeld/channel_wire.csv index 20e270661d32..c7505f5af338 100644 --- a/channeld/channel_wire.csv +++ b/channeld/channel_wire.csv @@ -208,3 +208,20 @@ msgdata,channel_send_error,reason,wirestring, # Tell master channeld has sent the error message. msgtype,channel_send_error_reply,1108 + +# Tell lightningd we got a onion message (for us, or to fwd) +msgtype,got_onionmsg_to_us,1142 +msgdata,got_onionmsg_to_us,reply_blinding,?pubkey, +msgdata,got_onionmsg_to_us,reply_path_len,u16, +msgdata,got_onionmsg_to_us,reply_path,onionmsg_path,reply_path_len + +msgtype,got_onionmsg_forward,1143 +msgdata,got_onionmsg_forward,next_scid,?short_channel_id, +msgdata,got_onionmsg_forward,next_node_id,?node_id, +msgdata,got_onionmsg_forward,next_blinding,?pubkey, +msgdata,got_onionmsg_forward,next_onion,u8,1366 + +# Lightningd tells us to send a onion message. +msgtype,send_onionmsg,1040 +msgdata,send_onionmsg,onion,u8,1366 +msgdata,send_onionmsg,blinding,?pubkey, diff --git a/channeld/channeld.c b/channeld/channeld.c index e3cba8f45070..cbb11862d1eb 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -1621,6 +1622,138 @@ static bool channeld_handle_custommsg(const u8 *msg) #endif } +#if EXPERIMENTAL_FEATURES +/* Peer sends onion msg. */ +static void handle_onion_message(struct peer *peer, const u8 *msg) +{ + enum onion_type badreason; + struct onionpacket op; + struct secret ss; + struct route_step *rs; + u8 onion[TOTAL_PACKET_SIZE]; + const u8 *cursor; + size_t max, maxlen; + struct tlv_onionmsg_payload *om; + const struct short_channel_id *next_scid; + struct node_id *next_node; + struct tlv_onion_message_tlvs *tlvs = tlv_onion_message_tlvs_new(msg); + + if (!fromwire_onion_message(msg, onion, tlvs)) + peer_failed(peer->pps, + &peer->channel_id, + "Bad onion_message %s", tal_hex(peer, msg)); + + if (tlvs->blinding) { + status_broken("FIXME: Handle blinding!"); + return; + } + + /* We unwrap the onion now. */ + badreason = parse_onionpacket(onion, TOTAL_PACKET_SIZE, &op); + if (badreason != 0) { + status_debug("onion msg: can't parse onionpacket: %s", + onion_type_name(badreason)); + return; + } + + /* Because wire takes struct pubkey. */ + msg = hsm_req(tmpctx, towire_hsm_ecdh_req(tmpctx, &op.ephemeralkey)); + if (!fromwire_hsm_ecdh_resp(msg, &ss)) + status_failed(STATUS_FAIL_HSM_IO, "Reading ecdh response"); + + /* We make sure we can parse onion packet, so we know if shared secret + * is actually valid (this checks hmac). */ + rs = process_onionpacket(tmpctx, &op, &ss, NULL, 0, false); + if (!rs) { + status_debug("onion msg: can't process onionpacket ss=%s", + type_to_string(tmpctx, struct secret, &ss)); + return; + } + + /* The raw payload is prepended with length in the TLV world. */ + cursor = rs->raw_payload; + max = tal_bytelen(rs->raw_payload); + maxlen = fromwire_bigsize(&cursor, &max); + if (!cursor) { + status_debug("onion msg: Invalid hop payload %s", + tal_hex(tmpctx, rs->raw_payload)); + return; + } + if (maxlen > max) { + status_debug("onion msg: overlong hop payload %s", + tal_hex(tmpctx, rs->raw_payload)); + return; + } + + om = tlv_onionmsg_payload_new(msg); + if (!fromwire_onionmsg_payload(&cursor, &maxlen, om)) { + status_debug("onion msg: invalid onionmsg_payload %s", + tal_hex(tmpctx, rs->raw_payload)); + return; + } + + if (om->next_short_channel_id) + next_scid = &om->next_short_channel_id->short_channel_id; + else + next_scid = NULL; + + if (om->next_node_id) { + next_node = tal(msg, struct node_id); + node_id_from_pubkey(next_node, &om->next_node_id->node_id); + } else + next_node = NULL; + + if (om->enctlv) { + status_broken("FIXME: Handle enctlv!"); + return; + } + + if (rs->nextcase == ONION_END) { + /* FIXME: Handle reply_path */ + /* payload may not be valid, so we hand it pre-decrypted to lightningd */ + wire_sync_write(MASTER_FD, + take(towire_got_onionmsg_to_us(NULL, + NULL, + NULL))); + } else { + /* This *MUST* have instructions on where to go next. */ + if (!next_scid && !next_node) { + status_debug("onion msg: no next field in %s", + tal_hex(tmpctx, rs->raw_payload)); + return; + } + + wire_sync_write(MASTER_FD, + take(towire_got_onionmsg_forward(NULL, + next_scid, + next_node, + NULL, + serialize_onionpacket(tmpctx, rs->next)))); + } +} + +/* We send onion msg. */ +static void send_onionmsg(struct peer *peer, const u8 *msg) +{ + u8 onion_routing_packet[TOTAL_PACKET_SIZE]; + struct pubkey *blinding; + struct tlv_onion_message_tlvs *tlvs = tlv_onion_message_tlvs_new(msg); + + if (!fromwire_send_onionmsg(msg, msg, onion_routing_packet, &blinding)) + master_badmsg(WIRE_SEND_ONIONMSG, msg); + + if (blinding) { + tlvs->blinding = tal(tlvs, + struct tlv_onion_message_tlvs_blinding); + tlvs->blinding->blinding = *blinding; + } + sync_crypto_write(peer->pps, + take(towire_onion_message(NULL, + onion_routing_packet, + tlvs))); +} +#endif /* EXPERIMENTAL_FEATURES */ + static void peer_in(struct peer *peer, const u8 *msg) { enum wire_type type = fromwire_peektype(msg); @@ -1694,7 +1827,8 @@ static void peer_in(struct peer *peer, const u8 *msg) return; #if EXPERIMENTAL_FEATURES case WIRE_ONION_MESSAGE: - break; + handle_onion_message(peer, msg); + return; #endif case WIRE_INIT: @@ -2684,6 +2818,14 @@ static void req_in(struct peer *peer, const u8 *msg) case WIRE_CHANNEL_SEND_ERROR: handle_send_error(peer, msg); return; +#if EXPERIMENTAL_FEATURES + case WIRE_SEND_ONIONMSG: + send_onionmsg(peer, msg); + return; +#else + case WIRE_SEND_ONIONMSG: + break; +#endif /* !EXPERIMENTAL_FEATURES */ #if DEVELOPER case WIRE_CHANNEL_DEV_REENABLE_COMMIT: handle_dev_reenable_commit(peer); @@ -2711,6 +2853,8 @@ static void req_in(struct peer *peer, const u8 *msg) case WIRE_CHANNEL_FAIL_FALLEN_BEHIND: case WIRE_CHANNEL_DEV_MEMLEAK_REPLY: case WIRE_CHANNEL_SEND_ERROR_REPLY: + case WIRE_GOT_ONIONMSG_TO_US: + case WIRE_GOT_ONIONMSG_FORWARD: break; } diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c index aeac9f14a8a5..b9d88c8c9ac2 100644 --- a/lightningd/channel_control.c +++ b/lightningd/channel_control.c @@ -319,6 +319,10 @@ static unsigned channel_msg(struct subd *sd, const u8 *msg, const int *fds) case WIRE_CHANNEL_SEND_ERROR_REPLY: handle_error_channel(sd->channel, msg); break; + case WIRE_GOT_ONIONMSG_TO_US: + case WIRE_GOT_ONIONMSG_FORWARD: + /* FIXME */ + break; /* And we never get these from channeld. */ case WIRE_CHANNEL_INIT: @@ -334,7 +338,8 @@ static unsigned channel_msg(struct subd *sd, const u8 *msg, const int *fds) case WIRE_CHANNEL_FEERATES: case WIRE_CHANNEL_SPECIFIC_FEERATES: case WIRE_CHANNEL_DEV_MEMLEAK: - /* Replies go to requests. */ + case WIRE_SEND_ONIONMSG: + /* Replies go to requests. */ case WIRE_CHANNEL_OFFER_HTLC_REPLY: case WIRE_CHANNEL_DEV_REENABLE_COMMIT_REPLY: case WIRE_CHANNEL_DEV_MEMLEAK_REPLY: diff --git a/tools/generate-wire.py b/tools/generate-wire.py index 547c769efb28..2c2958e19b9f 100755 --- a/tools/generate-wire.py +++ b/tools/generate-wire.py @@ -226,6 +226,7 @@ class Type(FieldSet): 'onionreply', 'witscript', 'feature_set', + 'onionmsg_path', ] # Some BOLT types are re-typed based on their field name From b5c0a9efce4b8e7bd2cd73e7a4e06e95645538f4 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 1 Apr 2020 14:23:22 +1030 Subject: [PATCH 04/11] lightningd: forward onion messages. Signed-off-by: Rusty Russell --- lightningd/Makefile | 1 + lightningd/channel_control.c | 11 ++++- lightningd/onion_message.c | 87 ++++++++++++++++++++++++++++++++++++ lightningd/onion_message.h | 11 +++++ 4 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 lightningd/onion_message.c create mode 100644 lightningd/onion_message.h diff --git a/lightningd/Makefile b/lightningd/Makefile index 69d6e0d65ac4..2ed6c9fa917b 100644 --- a/lightningd/Makefile +++ b/lightningd/Makefile @@ -75,6 +75,7 @@ LIGHTNINGD_SRC := \ lightningd/channel_control.c \ lightningd/closing_control.c \ lightningd/connect_control.c \ + lightningd/onion_message.c \ lightningd/gossip_control.c \ lightningd/gossip_msg.c \ lightningd/hsm_control.c \ diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c index b9d88c8c9ac2..01abc330351f 100644 --- a/lightningd/channel_control.c +++ b/lightningd/channel_control.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -319,11 +320,17 @@ static unsigned channel_msg(struct subd *sd, const u8 *msg, const int *fds) case WIRE_CHANNEL_SEND_ERROR_REPLY: handle_error_channel(sd->channel, msg); break; +#if EXPERIMENTAL_FEATURES case WIRE_GOT_ONIONMSG_TO_US: + handle_onionmsg_to_us(sd->channel, msg); + break; case WIRE_GOT_ONIONMSG_FORWARD: - /* FIXME */ + handle_onionmsg_forward(sd->channel, msg); break; - +#else + case WIRE_GOT_ONIONMSG_TO_US: + case WIRE_GOT_ONIONMSG_FORWARD: +#endif /* And we never get these from channeld. */ case WIRE_CHANNEL_INIT: case WIRE_CHANNEL_FUNDING_DEPTH: diff --git a/lightningd/onion_message.c b/lightningd/onion_message.c new file mode 100644 index 000000000000..70ee619f1eb4 --- /dev/null +++ b/lightningd/onion_message.c @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include + +#if EXPERIMENTAL_FEATURES +/* Returns false if we can't tell it */ +static bool make_peer_send(struct lightningd *ld, + struct channel *dst, const u8 *msg TAKES) +{ + /* Take ownership of msg (noop if it's taken) */ + msg = tal_dup_talarr(tmpctx, u8, msg); + + if (!dst) { + log_debug(ld->log, "Can't send %s: no channel", + channel_wire_type_name(fromwire_peektype(msg))); + return false; + } + + if (!dst->owner) { + log_debug(ld->log, "Can't send %s: not connected", + channel_wire_type_name(fromwire_peektype(msg))); + return false; + } + + /* FIXME: We should allow this for closingd too, and we should + * allow incoming via openingd!. */ + if (!streq(dst->owner->name, "channeld")) { + log_debug(ld->log, "Can't send %s: owned by %s", + channel_wire_type_name(fromwire_peektype(msg)), + dst->owner->name); + return false; + } + subd_send_msg(dst->owner, take(msg)); + return true; +} + +void handle_onionmsg_to_us(struct channel *channel, const u8 *msg) +{ + struct pubkey *reply_blinding; + struct onionmsg_path **reply_path; + + if (!fromwire_got_onionmsg_to_us(msg, msg, + &reply_blinding, &reply_path)) { + channel_internal_error(channel, "bad got_onionmsg_tous: %s", + tal_hex(tmpctx, msg)); + return; + } + + log_info(channel->log, "Got onionmsg%s%s", + reply_blinding ? " reply_blinding": "", + reply_path ? " reply_path": ""); +} + +void handle_onionmsg_forward(struct channel *channel, const u8 *msg) +{ + struct lightningd *ld = channel->peer->ld; + struct short_channel_id *next_scid; + struct node_id *next_node; + struct pubkey *next_blinding; + u8 onion[TOTAL_PACKET_SIZE]; + struct channel *outchan; + + if (!fromwire_got_onionmsg_forward(msg, msg, &next_scid, &next_node, + &next_blinding, onion)) { + channel_internal_error(channel, "bad got_onionmsg_forward: %s", + tal_hex(tmpctx, msg)); + return; + } + + if (next_scid) + outchan = active_channel_by_scid(ld, next_scid); + else if (next_node) { + struct peer *p = peer_by_id(ld, next_node); + if (p) + outchan = peer_active_channel(p); + else + outchan = NULL; + } else + outchan = NULL; + + make_peer_send(ld, outchan, + take(towire_send_onionmsg(NULL, onion, next_blinding))); +} +#endif /* EXPERIMENTAL_FEATURES */ diff --git a/lightningd/onion_message.h b/lightningd/onion_message.h new file mode 100644 index 000000000000..c2a2099a8ebb --- /dev/null +++ b/lightningd/onion_message.h @@ -0,0 +1,11 @@ +#ifndef LIGHTNING_LIGHTNINGD_ONION_MESSAGE_H +#define LIGHTNING_LIGHTNINGD_ONION_MESSAGE_H +#include "config.h" +#include + +struct channel; + +void handle_onionmsg_to_us(struct channel *channel, const u8 *msg); +void handle_onionmsg_forward(struct channel *channel, const u8 *msg); + +#endif /* LIGHTNING_LIGHTNINGD_ONION_MESSAGE_H */ From e2faf9c89eeae0d6132f99bd0d1f9319f222d7a5 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 1 Apr 2020 14:23:22 +1030 Subject: [PATCH 05/11] lightningd: EXPERIMENTAL_FEATURES JSON command to sendonionmessage. It's a fairly complex API, but maps simply onto the spec. Signed-off-by: Rusty Russell --- lightningd/onion_message.c | 239 +++++++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) diff --git a/lightningd/onion_message.c b/lightningd/onion_message.c index 70ee619f1eb4..7025ad0c5fdd 100644 --- a/lightningd/onion_message.c +++ b/lightningd/onion_message.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -84,4 +85,242 @@ void handle_onionmsg_forward(struct channel *channel, const u8 *msg) make_peer_send(ld, outchan, take(towire_send_onionmsg(NULL, onion, next_blinding))); } + +struct hop { + struct pubkey id; + struct short_channel_id *scid; + struct pubkey *blinding; + u8 *enctlv; + u8 *rawtlv; +}; + +static struct command_result *param_hops(struct command *cmd, + const char *name, + const char *buffer, + const jsmntok_t *tok, + struct hop **hops) +{ + size_t i; + const jsmntok_t *t; + + if (tok->type != JSMN_ARRAY || tok->size == 0) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "%s must be an (non-empty) array", name); + + *hops = tal_arr(cmd, struct hop, tok->size); + json_for_each_arr(i, t, tok) { + const jsmntok_t *tid, *tscid, *tblinding, *tenctlv, *trawtlv; + + tid = json_get_member(buffer, t, "id"); + if (!tid) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "%s[%zu] does not have 'id'", + name, i); + tscid = json_get_member(buffer, t, "short_channel_id"); + tblinding = json_get_member(buffer, t, "blinding"); + tenctlv = json_get_member(buffer, t, "enctlv"); + trawtlv = json_get_member(buffer, t, "rawtlv"); + + if (trawtlv && (tscid || tblinding || tenctlv)) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "%s[%zu] has 'rawtlv' with other fields", + name, i); + + if (tblinding) { + (*hops)[i].blinding = tal(*hops, struct pubkey); + if (!json_to_pubkey(buffer, tblinding, + (*hops)[i].blinding)) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "%s[%zu] 'blinding' is invalid", name, i); + } else + (*hops)[i].blinding = NULL; + + if (!json_to_pubkey(buffer, tid, &(*hops)[i].id)) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "%s[%zu] 'id' is invalid", name, i); + if (tscid) { + (*hops)[i].scid = tal(*hops, struct short_channel_id); + if (!json_to_short_channel_id(buffer, tscid, + (*hops)[i].scid)) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "%s[%zu] 'short_channel_id' is invalid", name, i); + } else + (*hops)[i].scid = NULL; + + if (tenctlv) { + (*hops)[i].enctlv = + json_tok_bin_from_hex(*hops, buffer, tenctlv); + if (!(*hops)[i].enctlv) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "%s[%zu] 'enctlv' is invalid", name, i); + } else + (*hops)[i].enctlv = NULL; + + if (trawtlv) { + (*hops)[i].rawtlv = + json_tok_bin_from_hex(*hops, buffer, trawtlv); + if (!(*hops)[i].rawtlv) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "%s[%zu] 'rawtlv' is invalid", name, i); + } else + (*hops)[i].rawtlv = NULL; + } + return NULL; +} + +static struct command_result *param_reply_path(struct command *cmd, + const char *name, + const char *buffer, + const jsmntok_t *tok, + struct tlv_onionmsg_payload_reply_path **reply_path) +{ + const jsmntok_t *tblinding, *tpath, *t; + size_t i; + + *reply_path = tal(cmd, struct tlv_onionmsg_payload_reply_path); + tblinding = json_get_member(buffer, tok, "blinding"); + if (!tblinding) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "%s has no 'blinding'", name); + if (!json_to_pubkey(buffer, tblinding, &(*reply_path)->blinding)) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "%s 'blinding' invalid pubkey", name); + + tpath = json_get_member(buffer, tok, "path"); + if (!tpath || tpath->type != JSMN_ARRAY) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "%s has no 'path' array", name); + + (*reply_path)->path = tal_arr(*reply_path, struct onionmsg_path *, + tpath->size); + json_for_each_arr(i, t, tpath) { + const jsmntok_t *tid, *tenctlv; + struct onionmsg_path *path; + + path = (*reply_path)->path[i] = tal((*reply_path)->path, + struct onionmsg_path); + tid = json_get_member(buffer, t, "id"); + if (!tid) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "%s path[%zu] 'id' is missing", + name, i); + if (!json_to_pubkey(buffer, tid, &path->node_id)) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "%s path[%zu] 'id' is invalid", + name, i); + + tenctlv = json_get_member(buffer, t, "enctlv"); + if (!tenctlv) { + /* Optional for final destination */ + if (i != tpath->size - 1) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "%s path[%zu] 'enctlv' is missing", + name, i); + path->enctlv = NULL; + } else { + path->enctlv = json_tok_bin_from_hex(path, + buffer, tenctlv); + if (!path->enctlv) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "%s path[%zu] 'enctlv' is invalid", + name, i); + } + } + + return NULL; +} + +/* Generate ->rawtlv if not already supplied. */ +static void populate_tlvs(struct hop *hops, + struct tlv_onionmsg_payload_reply_path *reply_path) +{ + for (size_t i = 0; i < tal_count(hops); i++) { + struct tlv_onionmsg_payload *tlv; + + if (hops[i].rawtlv) + continue; + + tlv = tlv_onionmsg_payload_new(tmpctx); + /* If they don't give scid, use next node id */ + if (hops[i].scid) { + tlv->next_short_channel_id = tal(tlv, struct tlv_onionmsg_payload_next_short_channel_id); + tlv->next_short_channel_id->short_channel_id = *hops[i].scid; + } else if (i != tal_count(hops)-1) { + tlv->next_node_id = tal(tlv, struct tlv_onionmsg_payload_next_node_id); + tlv->next_node_id->node_id = hops[i+1].id; + } + if (hops[i].blinding) { + tlv->blinding = tal(tlv, struct tlv_onionmsg_payload_blinding); + tlv->blinding->blinding = *hops[i].blinding; + } + if (hops[i].enctlv) { + tlv->enctlv = tal(tlv, struct tlv_onionmsg_payload_enctlv); + tlv->enctlv->enctlv = hops[i].enctlv; + } + + if (i == tal_count(hops)-1 && reply_path) + tlv->reply_path = reply_path; + + hops[i].rawtlv = tal_arr(hops, u8, 0); + towire_onionmsg_payload(&hops[i].rawtlv, tlv); + } +} + +static struct command_result *json_send_onion_message(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) +{ + struct hop *hops; + struct tlv_onionmsg_payload_reply_path *reply_path; + struct sphinx_path *sphinx_path; + struct onionpacket *op; + struct secret *path_secrets; + struct channel *first_hop; + struct node_id first_id; + + if (!param(cmd, buffer, params, + p_req("hops", param_hops, &hops), + p_opt("reply_path", param_reply_path, &reply_path), + NULL)) + return command_param_failed(); + + /* FIXME: Allow sending to non-channel peers! */ + node_id_from_pubkey(&first_id, &hops[0].id); + first_hop = active_channel_by_id(cmd->ld, &first_id, NULL); + if (!first_hop) + return command_fail(cmd, LIGHTNINGD, "Unknown first peer"); + + /* Create an onion which encodes this. */ + populate_tlvs(hops, reply_path); + sphinx_path = sphinx_path_new(cmd, NULL); + for (size_t i = 0; i < tal_count(hops); i++) { + /* FIXME: Remove legacy, then length prefix can be removed! */ + u8 *tlv_with_len = tal_arr(NULL, u8, 0); + towire_bigsize(&tlv_with_len, tal_bytelen(hops[i].rawtlv)); + towire_u8_array(&tlv_with_len, + hops[i].rawtlv, tal_bytelen(hops[i].rawtlv)); + sphinx_add_hop(sphinx_path, &hops[i].id, take(tlv_with_len)); + } + op = create_onionpacket(tmpctx, sphinx_path, &path_secrets); + if (!op) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Creating onion failed (tlvs too long?)"); + + if (!make_peer_send(cmd->ld, first_hop, + take(towire_send_onionmsg(NULL, + serialize_onionpacket(tmpctx, op), + NULL)))) + return command_fail(cmd, LIGHTNINGD, "First peer not ready"); + + return command_success(cmd, json_stream_success(cmd)); +} + +static const struct json_command send_onion_message_command = { + "sendonionmessage", + "utility", + json_send_onion_message, + "Send message over {hops} (id, [short_channel_id], [blinding], [enctlv], [rawtlv]) with optional {reply_path} (blinding, path[id, enctlv])" +}; +AUTODATA(json_command, &send_onion_message_command); #endif /* EXPERIMENTAL_FEATURES */ From 7bdab859494ba34952118d751d22682086e5482e Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 1 Apr 2020 14:23:22 +1030 Subject: [PATCH 06/11] pytest: test sendonionmessage. Signed-off-by: Rusty Russell --- tests/test_misc.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/test_misc.py b/tests/test_misc.py index 5a34aaeeb504..aecd96ae4c2f 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -10,6 +10,7 @@ TailableProc, env ) from ephemeral_port_reserve import reserve +from utils import EXPERIMENTAL_FEATURES import json import os @@ -2177,6 +2178,27 @@ def test_sendcustommsg(node_factory): serialized=serialized, peer_id=l2.info['id'])) +@unittest.skipIf(not EXPERIMENTAL_FEATURES, "Needs sendonionmessage") +def test_sendonionmessage(node_factory): + l1, l2, l3 = node_factory.line_graph(3) + + l1.rpc.call('sendonionmessage', + {'hops': + [{'id': l2.info['id']}, + {'id': l3.info['id']}]}) + assert l3.daemon.wait_for_log('Got onionmsg') + + # Now by SCID. + l1.rpc.call('sendonionmessage', + {'hops': + [{'id': l2.info['id'], + 'short_channel_id': l2.get_channel_scid(l3)}, + {'id': l3.info['id']}]}) + assert l3.daemon.wait_for_log('Got onionmsg') + + # FIXME: Test blinded path! + + @unittest.skipIf(not DEVELOPER, "needs --dev-force-privkey") def test_getsharedsecret(node_factory): """ From 3b252b064766074e702d89e983a50533a24d9d40 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 1 Apr 2020 14:23:22 +1030 Subject: [PATCH 07/11] devtool/blindedpath: primitive tool to make blinded onions. e.g. $ PUBKEY1=0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518 $ PRIVKEY1=41bfd2660762506c9933ade59f1debf7e6495b10c14a92dbcd2d623da2507d3d $ PUBKEY2=022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59 $ PRIVKEY1=c4a813f81ffdca1da6864db81795ad2d320add274452cafa1fb2ac2d07d062bd # First line is blinding, second is contents and nodeids for onion. $ ./devtools/blindedpath create $PUBKEY1 $PUBKEY2 03f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518/350633c340f28bc69cbc86f568b7b9e99fa41eb581452d066fcd70dd53c43ace14d034eebfbe472a2b9901b11c268d2cc2034a77928a 0326f31ff78e584461420e5026fe72374af2ef853e65c47a3f2406348b7c6c0911/00 # Generate the onion $ /devtools/onion generate 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518/350633c340f28bc69cbc86f568b7b9e99fa41eb581452d066fcd70dd53c43ace14d034eebfbe472a2b9901b11c268d2cc2034a77928a 0326f31ff78e584461420e5026fe72374af2ef853e65c47a3f2406348b7c6c0911/00 > /tmp/onion.dat # First node unwraps it, gives next blinding and onion $ ./devtools/blindedpath --first-node unwrap $PRIVKEY1 `cat /tmp/onion.dat` 03f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a Contents: 04210326f31ff78e584461420e5026fe72374af2ef853e65c47a3f2406348b7c6c0911 Next blinding: 021295ce94fcadc42c3e5187a12dd80122214c8f9da61635163cddb63282f1ee9b Next onion: 0003c8fa9d4997ebd049480db14f0a90db211ec2b9f062e569419ee2c2b528a2d7adc63aa9e7b74997c2c122c4c1923e2f4587fc62532965666dbe55a76aa1ea903cfe6c498c6b7e80c14609d3c0f98f86a30f94b0b8a405067ee3801aab03420922cdc847d6f2fc359655408047a8d7d7892a595f630cdf114d1cc0d1164dc8099378042cfba7a13711dff64000356aac29726a6039bf938b81cc0dcc7f65dc126ae58838c0446d6492b6381f0402a33318a9ae71486bcb4b58f66c9a056fca306668655c11f7d7d0cd447e4162100565369629ca8b705b2b999a40ad5493953cb70b35f382e6acdc04e5a933783f9c5859fb0beeaa9c54e5220f5de3b107813d33148501aebcf67e190d3dcf10553714d4de8a1643b519cd124da9a345e2da0d669954a10fce9c1e7795572fb2ac8fe6de4db856bdbd327f0c4ae3cb11f6e1422f663423ad57891d069bbc5bdc7613c742a2227d3789d9039dcdcbddc2703835002dd176004c56cc497d88deae2328ad1376877f4582c71a7fa1eba4ae4e6696782bd97d7b362f41e81335b47273a74c983c3bc80499069a08c6b7ffa32cc77f54a98d8bc2f80f38c370c98edd8f6d6f95c6bbf5c8040296f68560de3b50c3450de4fcceae41469bea6a24c83141c92956fa4c4087f11e9c26b282e9c3974fadc8f9bb9fb9c3fbd2ead0cf4ceaba452eb8791828a159ace7a2e1e8ff5c69704a821c6c898a3c38439149862f14b7ed34afb93640c3ae61b089011ef698c9e26dc16b8a5a8ec66fce702b7bdf04a21cf9a2ebff6b89b29904e7e3e6a98088c2848951c0d6915249a3e1199c2affe4635ac6ef3a16ddfefc5790435a14067b24d5ecd16a26d2f7dbd8065b8e5b86f73f878cf55ca0c9f12104d861d03ac5c4b3dcbd0e30ad7ba888ddcc2e89acb3c04be2ef4bce3ef8c8878fef5be65664f1ec288f91dbd1748e2e53bcbd7dc9cecd75a246992e76a844ab122c5e179f97531190c7d91586289410c9ddad33eb156ab7312c82e55f3d643f3c12468ce79f7221051da608dd17ceb235b7df89f3c4b9aa9448bf36206b2db7bb97f544d062d6aa1b1706376fb6e3e8ef1ac293b9adca478458e9e51845dc7b554c70a91e32c331962968f98db26faa5b10a39bc778b0aab5a9fd11fdbab7b456db286049e584b7e4d1c76d6c3c6249b567aa357982ceead2ad8d5113a866818997b24018059e93eb5cafd293942efec3cf4a43bf322cd444e8370dc2cf1f1164c5147b30a791e262acadc15c30f1e169af4bff5e6c098acf95534b0b59517e3168413134984d50c8242590c8729fe34190d20d7f88505747b919e0bf8b41ed4ec9146743339c6885dcb770dcac627a1570dc145f6f61a976d87d16473195d5f5ce39347ab040c34fe0888b498f0ba25686a9bae51f6e5973d15f10d1c7dbe5fbfa2c7fe05cdd52d8eca8150914b4cb2e75e52010bb4b9241160d7337f47bbebd3bea58ddcf320a0464c34dbfd3d3ea7ffaee966c36064b2cc77babbf4613a7d5d65b3931dc42f91ed902207c57509a46738d31671c439d052c16db5ce3e613c5b37f77e574e9a847ebab20159130f33186557d16ddd1d765e7a9adedb253b755128e1af58da9e8b0fe6ed5834ddf1537c0ee78cd9803ee06031c3c1331d405a94a54f06d1147b26d0788179320d882f57ee9c63ab7a76fb9572eb813fe55369b5061b0a242b07a72095754a9d5699534a46829398204e76f1eda1d65e98fab1e8f3cf7c85257c8e2da0546fed215c3db38d231a637fd7a4e6f10b786d15534137489c662a0f289da824ca8dfef335bdcc623d636d231e002cc32febbe6683046ee54702dbeb55a70911505844c79c0be4630cf49456fba55aec9218d3ba449540370d407fc653007fdf59b4711d1da3c2e569eec4de8cee7b05d248ad0 # Feed that onion and blinding to second node $ ./devtools/blindedpath unwrap $PRIVKEY2 0003c8fa9d4997ebd049480db14f0a90db211ec2b9f062e569419ee2c2b528a2d7adc63aa9e7b74997c2c122c4c1923e2f4587fc62532965666dbe55a76aa1ea903cfe6c498c6b7e80c14609d3c0f98f86a30f94b0b8a405067ee3801aab03420922cdc847d6f2fc359655408047a8d7d7892a595f630cdf114d1cc0d1164dc8099378042cfba7a13711dff64000356aac29726a6039bf938b81cc0dcc7f65dc126ae58838c0446d6492b6381f0402a33318a9ae71486bcb4b58f66c9a056fca306668655c11f7d7d0cd447e4162100565369629ca8b705b2b999a40ad5493953cb70b35f382e6acdc04e5a933783f9c5859fb0beeaa9c54e5220f5de3b107813d33148501aebcf67e190d3dcf10553714d4de8a1643b519cd124da9a345e2da0d669954a10fce9c1e7795572fb2ac8fe6de4db856bdbd327f0c4ae3cb11f6e1422f663423ad57891d069bbc5bdc7613c742a2227d3789d9039dcdcbddc2703835002dd176004c56cc497d88deae2328ad1376877f4582c71a7fa1eba4ae4e6696782bd97d7b362f41e81335b47273a74c983c3bc80499069a08c6b7ffa32cc77f54a98d8bc2f80f38c370c98edd8f6d6f95c6bbf5c8040296f68560de3b50c3450de4fcceae41469bea6a24c83141c92956fa4c4087f11e9c26b282e9c3974fadc8f9bb9fb9c3fbd2ead0cf4ceaba452eb8791828a159ace7a2e1e8ff5c69704a821c6c898a3c38439149862f14b7ed34afb93640c3ae61b089011ef698c9e26dc16b8a5a8ec66fce702b7bdf04a21cf9a2ebff6b89b29904e7e3e6a98088c2848951c0d6915249a3e1199c2affe4635ac6ef3a16ddfefc5790435a14067b24d5ecd16a26d2f7dbd8065b8e5b86f73f878cf55ca0c9f12104d861d03ac5c4b3dcbd0e30ad7ba888ddcc2e89acb3c04be2ef4bce3ef8c8878fef5be65664f1ec288f91dbd1748e2e53bcbd7dc9cecd75a246992e76a844ab122c5e179f97531190c7d91586289410c9ddad33eb156ab7312c82e55f3d643f3c12468ce79f7221051da608dd17ceb235b7df89f3c4b9aa9448bf36206b2db7bb97f544d062d6aa1b1706376fb6e3e8ef1ac293b9adca478458e9e51845dc7b554c70a91e32c331962968f98db26faa5b10a39bc778b0aab5a9fd11fdbab7b456db286049e584b7e4d1c76d6c3c6249b567aa357982ceead2ad8d5113a866818997b24018059e93eb5cafd293942efec3cf4a43bf322cd444e8370dc2cf1f1164c5147b30a791e262acadc15c30f1e169af4bff5e6c098acf95534b0b59517e3168413134984d50c8242590c8729fe34190d20d7f88505747b919e0bf8b41ed4ec9146743339c6885dcb770dcac627a1570dc145f6f61a976d87d16473195d5f5ce39347ab040c34fe0888b498f0ba25686a9bae51f6e5973d15f10d1c7dbe5fbfa2c7fe05cdd52d8eca8150914b4cb2e75e52010bb4b9241160d7337f47bbebd3bea58ddcf320a0464c34dbfd3d3ea7ffaee966c36064b2cc77babbf4613a7d5d65b3931dc42f91ed902207c57509a46738d31671c439d052c16db5ce3e613c5b37f77e574e9a847ebab20159130f33186557d16ddd1d765e7a9adedb253b755128e1af58da9e8b0fe6ed5834ddf1537c0ee78cd9803ee06031c3c1331d405a94a54f06d1147b26d0788179320d882f57ee9c63ab7a76fb9572eb813fe55369b5061b0a242b07a72095754a9d5699534a46829398204e76f1eda1d65e98fab1e8f3cf7c85257c8e2da0546fed215c3db38d231a637fd7a4e6f10b786d15534137489c662a0f289da824ca8dfef335bdcc623d636d231e002cc32febbe6683046ee54702dbeb55a70911505844c79c0be4630cf49456fba55aec9218d3ba449540370d407fc653007fdf59b4711d1da3c2e569eec4de8cee7b05d248ad0 021295ce94fcadc42c3e5187a12dd80122214c8f9da61635163cddb63282f1ee9b Signed-off-by: Rusty Russell Header from folded patch 'fixup': fixup! devtool/blindedpath: primitive tool to make blinded onions. On decode, don't mess with op.ephemeralkey, since it will be used to derive the next hop. Signed-off-by: Rusty Russell --- devtools/Makefile | 6 + devtools/blindedpath.c | 316 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 322 insertions(+) create mode 100644 devtools/blindedpath.c diff --git a/devtools/Makefile b/devtools/Makefile index 0ca66b4f1579..a272feea5f13 100644 --- a/devtools/Makefile +++ b/devtools/Makefile @@ -1,6 +1,10 @@ DEVTOOLS_SRC := devtools/gen_print_wire.c devtools/gen_print_onion_wire.c devtools/print_wire.c DEVTOOLS_OBJS := $(DEVTOOLS_SRC:.c=.o) DEVTOOLS := devtools/bolt11-cli devtools/decodemsg devtools/onion devtools/dump-gossipstore devtools/gossipwith devtools/create-gossipstore devtools/mkcommit devtools/mkfunding devtools/mkclose devtools/mkgossip devtools/mkencoded devtools/checkchannels devtools/mkquery devtools/lightning-checkmessage +ifeq ($(EXPERIMENTAL_FEATURES),1) +DEVTOOLS += devtools/blindedpath +endif + DEVTOOLS_TOOL_SRC := $(DEVTOOLS:=.c) DEVTOOLS_TOOL_OBJS := $(DEVTOOLS_TOOL_SRC:.c=.o) @@ -66,6 +70,8 @@ devtools/onion.c: ccan/config.h devtools/onion: $(DEVTOOLS_OBJS) $(DEVTOOLS_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS) $(BITCOIN_OBJS) wire/fromwire.o wire/towire.o devtools/onion.o common/sphinx.o +devtools/blindedpath: $(DEVTOOLS_OBJS) $(DEVTOOLS_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS) $(BITCOIN_OBJS) wire/fromwire.o wire/towire.o devtools/blindedpath.o common/sphinx.o + devtools/gossipwith: $(DEVTOOLS_OBJS) $(DEVTOOLS_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS) $(BITCOIN_OBJS) wire/fromwire.o wire/towire.o wire/gen_peer_wire.o devtools/gossipwith.o common/cryptomsg.o common/cryptomsg.o common/crypto_sync.o $(DEVTOOLS_OBJS) $(DEVTOOLS_TOOL_OBJS): wire/wire.h devtools/gen_print_wire.h devtools/gen_print_onion_wire.h diff --git a/devtools/blindedpath.c b/devtools/blindedpath.c new file mode 100644 index 000000000000..d21490b678bf --- /dev/null +++ b/devtools/blindedpath.c @@ -0,0 +1,316 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Tal wrappers for opt. */ +static void *opt_allocfn(size_t size) +{ + return tal_arr_label(NULL, char, size, TAL_LABEL("opt_allocfn", "")); +} + +static void *tal_reallocfn(void *ptr, size_t size) +{ + if (!ptr) + return opt_allocfn(size); + tal_resize_(&ptr, 1, size, false); + return ptr; +} + +static void tal_freefn(void *ptr) +{ + tal_free(ptr); +} + +/* E(i-1) = H(E(i) || ss(i)) * E(i) */ +static struct sha256 hash_e_and_ss(const struct pubkey *e, + const struct secret *ss) +{ + u8 der[PUBKEY_CMPR_LEN]; + struct sha256_ctx shactx; + struct sha256 h; + + pubkey_to_der(der, e); + sha256_init(&shactx); + sha256_update(&shactx, der, sizeof(der)); + sha256_update(&shactx, ss->data, sizeof(ss->data)); + sha256_done(&shactx, &h); + + return h; +} + +/* E(i-1) = H(E(i) || ss(i)) * E(i) */ +static struct pubkey next_pubkey(const struct pubkey *pk, + const struct sha256 *h) +{ + struct pubkey ret; + + ret = *pk; + if (secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx, &ret.pubkey, h->u.u8) + != 1) + abort(); + + return ret; +} + +/* e(i+1) = H(E(i) || ss(i)) * e(i) */ +static struct privkey next_privkey(const struct privkey *e, + const struct sha256 *h) +{ + struct privkey ret; + + ret = *e; + if (secp256k1_ec_privkey_tweak_mul(secp256k1_ctx, ret.secret.data, + h->u.u8) != 1) + abort(); + + return ret; +} + +int main(int argc, char **argv) +{ + bool first = false; + + setup_locale(); + + secp256k1_ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | + SECP256K1_CONTEXT_SIGN); + + opt_set_alloc(opt_allocfn, tal_reallocfn, tal_freefn); + opt_register_noarg("--help|-h", opt_usage_and_exit, + "\n\n\tcreate [/]...\n" + "\tunwrap \n", + "Show this message"); + opt_register_noarg("--first-node", opt_set_bool, &first, + "Don't try to tweak key to unwrap onion"); + opt_register_version(); + + opt_parse(&argc, argv, opt_log_stderr_exit); + setup_tmpctx(); + + if (argc < 2) + errx(1, "You must specify create or unwrap"); + if (streq(argv[1], "create")) { + struct privkey e; + struct pubkey *pk_e, *b, *nodes; + struct secret *rho; + size_t num = argc - 2; + + if (argc < 3) + errx(1, "create requires at least one nodeid"); + + /* P(i) */ + nodes = tal_arr(tmpctx, struct pubkey, num); + /* E(i) */ + pk_e = tal_arr(tmpctx, struct pubkey, num); + /* B(i) */ + b = tal_arr(tmpctx, struct pubkey, num); + /* rho(i) */ + rho = tal_arr(tmpctx, struct secret, num); + + /* Randomness, chosen with a fair dice roll! */ + memset(&e, 6, sizeof(e)); + if (!pubkey_from_privkey(&e, &pk_e[0])) + abort(); + + for (size_t i = 0; i < num; i++) { + struct secret ss; + struct secret hmac; + struct sha256 h; + + if (!pubkey_from_hexstr(argv[2+i], + strcspn(argv[2+i], "/"), + &nodes[i])) + errx(1, "%s not a valid pubkey", argv[2+i]); + + if (secp256k1_ecdh(secp256k1_ctx, ss.data, + &nodes[i].pubkey, e.secret.data, NULL, NULL) != 1) + abort(); + + subkey_from_hmac("blinded_node_id", &ss, &hmac); + b[i] = nodes[i]; + if (i != 0) { + if (secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx, + &b[i].pubkey, hmac.data) != 1) + abort(); + } + subkey_from_hmac("rho", &ss, &rho[i]); + h = hash_e_and_ss(&pk_e[i], &ss); + if (i != num-1) + pk_e[i+1] = next_pubkey(&pk_e[i], &h); + e = next_privkey(&e, &h); + } + + /* Print initial blinding factor */ + printf("Blinding: %s\n", + type_to_string(tmpctx, struct pubkey, &pk_e[0])); + + for (size_t i = 0; i < num - 1; i++) { + u8 *p; + u8 buf[BIGSIZE_MAX_LEN]; + const unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + struct tlv_onionmsg_payload *inner, *outer; + int ret; + + /* Inner is encrypted */ + inner = tlv_onionmsg_payload_new(tmpctx); + /* FIXME: Use /scid for encblob if specified */ + inner->next_node_id = tal(inner, struct tlv_onionmsg_payload_next_node_id); + inner->next_node_id->node_id = nodes[i+1]; + p = tal_arr(tmpctx, u8, 0); + towire_encmsg_tlvs(&p, inner); + + outer = tlv_onionmsg_payload_new(tmpctx); + outer->enctlv = tal(outer, struct tlv_onionmsg_payload_enctlv); + outer->enctlv->enctlv = tal_arr(tmpctx, u8, tal_count(p) + + crypto_aead_chacha20poly1305_ietf_ABYTES); + ret = crypto_aead_chacha20poly1305_ietf_encrypt(outer->enctlv->enctlv, NULL, + p, + tal_bytelen(p), + NULL, 0, + NULL, npub, + rho[i].data); + assert(ret == 0); + + p = tal_arr(tmpctx, u8, 0); + towire_onionmsg_payload(&p, outer); + ret = bigsize_put(buf, tal_bytelen(p)); + + /* devtools/onion wants length explicitly prepended */ + printf("%s/%.*s%s ", + type_to_string(tmpctx, struct pubkey, &b[i]), + ret * 2, + tal_hexstr(tmpctx, buf, ret), + tal_hex(tmpctx, p)); + } + /* No payload for last node */ + printf("%s/00\n", + type_to_string(tmpctx, struct pubkey, &b[num-1])); + } else if (streq(argv[1], "unwrap")) { + struct privkey privkey; + struct pubkey blinding; + u8 onion[TOTAL_PACKET_SIZE], *dec; + struct onionpacket op; + struct secret ss, onion_ss; + struct secret hmac, rho; + struct route_step *rs; + const u8 *cursor; + struct tlv_onionmsg_payload *outer; + size_t max, len; + struct pubkey res; + struct sha256 h; + int ret; + const unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + if (argc != 5) + errx(1, "unwrap requires privkey, onion and blinding"); + + if (!hex_decode(argv[2], strlen(argv[2]), &privkey, + sizeof(privkey))) + errx(1, "Invalid private key hex '%s'", argv[2]); + + if (!hex_decode(argv[3], strlen(argv[3]), onion, + sizeof(onion))) + errx(1, "Invalid onion %s", argv[3]); + + if (!pubkey_from_hexstr(argv[4], strlen(argv[4]), &blinding)) + errx(1, "Invalid blinding %s", argv[4]); + + if (parse_onionpacket(onion, sizeof(onion), &op) != 0) + errx(1, "Unparsable onion"); + + /* ss(r) = H(k(r) * E(r)) */ + if (secp256k1_ecdh(secp256k1_ctx, ss.data, &blinding.pubkey, + privkey.secret.data, NULL, NULL) != 1) + abort(); + + subkey_from_hmac("rho", &ss, &rho); + + /* b(i) = HMAC256("blinded_node_id", ss(i)) * k(i) */ + subkey_from_hmac("blinded_node_id", &ss, &hmac); + + /* We instead tweak the *ephemeral* key from the onion + * and use our raw privkey: this models how lightningd + * will do it, since hsmd knows only how to ECDH with + * our real key */ + res = op.ephemeralkey; + if (!first) { + if (secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx, + &res.pubkey, + hmac.data) != 1) + abort(); + } + + if (secp256k1_ecdh(secp256k1_ctx, onion_ss.data, + &res.pubkey, + privkey.secret.data, NULL, NULL) != 1) + abort(); + + rs = process_onionpacket(tmpctx, &op, &onion_ss, NULL, 0, false); + if (!rs) + errx(1, "Could not process onionpacket"); + + cursor = rs->raw_payload; + max = tal_bytelen(cursor); + len = fromwire_bigsize(&cursor, &max); + + /* Always true since we're non-legacy */ + assert(len == max); + outer = tlv_onionmsg_payload_new(tmpctx); + if (!fromwire_onionmsg_payload(&cursor, &max, outer)) + errx(1, "Invalid payload %s", + tal_hex(tmpctx, rs->raw_payload)); + + if (rs->nextcase == ONION_END) { + printf("TERMINAL\n"); + return 0; + } + + /* Look for enctlv */ + if (!outer->enctlv) + errx(1, "No enctlv field"); + + if (tal_bytelen(outer->enctlv->enctlv) + < crypto_aead_chacha20poly1305_ietf_ABYTES) + errx(1, "enctlv field too short"); + + dec = tal_arr(tmpctx, u8, + tal_bytelen(outer->enctlv->enctlv) + - crypto_aead_chacha20poly1305_ietf_ABYTES); + ret = crypto_aead_chacha20poly1305_ietf_decrypt(dec, NULL, + NULL, + outer->enctlv->enctlv, + tal_bytelen(outer->enctlv->enctlv), + NULL, 0, + npub, + rho.data); + if (ret != 0) + errx(1, "Failed to decrypt enctlv field"); + + printf("Contents: %s\n", tal_hex(tmpctx, dec)); + + /* E(i-1) = H(E(i) || ss(i)) * E(i) */ + h = hash_e_and_ss(&blinding, &ss); + res = next_pubkey(&blinding, &h); + printf("Next blinding: %s\n", + type_to_string(tmpctx, struct pubkey, &res)); + printf("Next onion: %s\n", tal_hex(tmpctx, serialize_onionpacket(tmpctx, rs->next))); + } else + errx(1, "Either create or unwrap!"); +} From 556df4fab60759a6f902d7d2453e04b4d01a2c21 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 1 Apr 2020 14:23:22 +1030 Subject: [PATCH 08/11] devtools/blindedpath: add --simple-output for use from python. Normal output is suitable for feeding to devtools/onion, but for python tests we want something simpler. Ideally, we'd simply generate blinded paths in pyln. Signed-off-by: Rusty Russell --- devtools/blindedpath.c | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/devtools/blindedpath.c b/devtools/blindedpath.c index d21490b678bf..469ebebf8f42 100644 --- a/devtools/blindedpath.c +++ b/devtools/blindedpath.c @@ -19,6 +19,8 @@ #include #include +static bool simpleout = false; + /* Tal wrappers for opt. */ static void *opt_allocfn(size_t size) { @@ -99,6 +101,8 @@ int main(int argc, char **argv) "Show this message"); opt_register_noarg("--first-node", opt_set_bool, &first, "Don't try to tweak key to unwrap onion"); + opt_register_noarg("--simple-output", opt_set_bool, &simpleout, + "Output values without prefixes, one per line"); opt_register_version(); opt_parse(&argc, argv, opt_log_stderr_exit); @@ -158,8 +162,12 @@ int main(int argc, char **argv) } /* Print initial blinding factor */ - printf("Blinding: %s\n", - type_to_string(tmpctx, struct pubkey, &pk_e[0])); + if (simpleout) + printf("%s\n", + type_to_string(tmpctx, struct pubkey, &pk_e[0])); + else + printf("Blinding: %s\n", + type_to_string(tmpctx, struct pubkey, &pk_e[0])); for (size_t i = 0; i < num - 1; i++) { u8 *p; @@ -192,16 +200,28 @@ int main(int argc, char **argv) towire_onionmsg_payload(&p, outer); ret = bigsize_put(buf, tal_bytelen(p)); - /* devtools/onion wants length explicitly prepended */ - printf("%s/%.*s%s ", - type_to_string(tmpctx, struct pubkey, &b[i]), - ret * 2, - tal_hexstr(tmpctx, buf, ret), - tal_hex(tmpctx, p)); + if (simpleout) { + printf("%s\n%s\n", + type_to_string(tmpctx, struct pubkey, + &b[i]), + tal_hex(tmpctx, outer->enctlv->enctlv)); + } else { + /* devtools/onion wants length explicitly prepended */ + printf("%s/%.*s%s ", + type_to_string(tmpctx, struct pubkey, + &b[i]), + ret * 2, + tal_hexstr(tmpctx, buf, ret), + tal_hex(tmpctx, p)); + } } /* No payload for last node */ - printf("%s/00\n", - type_to_string(tmpctx, struct pubkey, &b[num-1])); + if (simpleout) + printf("%s\n", + type_to_string(tmpctx, struct pubkey, &b[num-1])); + else + printf("%s/00\n", + type_to_string(tmpctx, struct pubkey, &b[num-1])); } else if (streq(argv[1], "unwrap")) { struct privkey privkey; struct pubkey blinding; From 7d9abc210cbdbda3ae62297eb220e7d8f7faaf57 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 1 Apr 2020 14:23:22 +1030 Subject: [PATCH 09/11] channeld: handle encblob and blinding in messages. This is based on https://github.com/lightningnetwork/lightning-rfc/blob/route-blinding/proposals/route-blinding.md Signed-off-by: Rusty Russell --- channeld/channeld.c | 168 ++++++++++++++++++++++++++++++++++++++++---- tests/test_misc.py | 20 +++++- 2 files changed, 175 insertions(+), 13 deletions(-) diff --git a/channeld/channeld.c b/channeld/channeld.c index cbb11862d1eb..a6643e9c2706 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -55,6 +55,7 @@ #include #include #include +#include #include #include #include @@ -1623,12 +1624,44 @@ static bool channeld_handle_custommsg(const u8 *msg) } #if EXPERIMENTAL_FEATURES +/* H(E(i) || ss(i)) */ +static struct sha256 hash_e_and_ss(const struct pubkey *e, + const struct secret *ss) +{ + u8 der[PUBKEY_CMPR_LEN]; + struct sha256_ctx shactx; + struct sha256 h; + + pubkey_to_der(der, e); + sha256_init(&shactx); + sha256_update(&shactx, der, sizeof(der)); + sha256_update(&shactx, ss->data, sizeof(ss->data)); + sha256_done(&shactx, &h); + + return h; +} + +/* E(i-1) = H(E(i) || ss(i)) * E(i) */ +static struct pubkey next_pubkey(const struct pubkey *pk, + const struct sha256 *h) +{ + struct pubkey ret; + + ret = *pk; + if (secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx, &ret.pubkey, h->u.u8) + != 1) + abort(); + + return ret; +} + /* Peer sends onion msg. */ static void handle_onion_message(struct peer *peer, const u8 *msg) { enum onion_type badreason; struct onionpacket op; - struct secret ss; + struct secret ss, *blinding_ss; + struct pubkey *blinding_in; struct route_step *rs; u8 onion[TOTAL_PACKET_SIZE]; const u8 *cursor; @@ -1643,11 +1676,6 @@ static void handle_onion_message(struct peer *peer, const u8 *msg) &peer->channel_id, "Bad onion_message %s", tal_hex(peer, msg)); - if (tlvs->blinding) { - status_broken("FIXME: Handle blinding!"); - return; - } - /* We unwrap the onion now. */ badreason = parse_onionpacket(onion, TOTAL_PACKET_SIZE, &op); if (badreason != 0) { @@ -1656,7 +1684,38 @@ static void handle_onion_message(struct peer *peer, const u8 *msg) return; } - /* Because wire takes struct pubkey. */ + if (tlvs->blinding) { + struct secret hmac; + + /* E(i) */ + blinding_in = tal(msg, struct pubkey); + *blinding_in = tlvs->blinding->blinding; + status_debug("blinding in = %s", + type_to_string(tmpctx, struct pubkey, blinding_in)); + blinding_ss = tal(msg, struct secret); + msg = hsm_req(tmpctx, towire_hsm_ecdh_req(tmpctx, blinding_in)); + + if (!fromwire_hsm_ecdh_resp(msg, blinding_ss)) + status_failed(STATUS_FAIL_HSM_IO, + "Reading ecdh response for blinding"); + + /* b(i) = HMAC256("blinded_node_id", ss(i)) * k(i) */ + subkey_from_hmac("blinded_node_id", blinding_ss, &hmac); + + /* We instead tweak the *ephemeral* key from the onion and use + * our normal privkey: since hsmd knows only how to ECDH with + * our real key */ + if (secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx, + &op.ephemeralkey.pubkey, + hmac.data) != 1) { + status_debug("onion msg: can't tweak pubkey"); + return; + } + } else { + blinding_ss = NULL; + blinding_in = NULL; + } + msg = hsm_req(tmpctx, towire_hsm_ecdh_req(tmpctx, &op.ephemeralkey)); if (!fromwire_hsm_ecdh_resp(msg, &ss)) status_failed(STATUS_FAIL_HSM_IO, "Reading ecdh response"); @@ -1692,6 +1751,72 @@ static void handle_onion_message(struct peer *peer, const u8 *msg) return; } + /* If we weren't given a blinding factor, tlv can provide one. */ + if (om->blinding && !blinding_ss) { + /* E(i) */ + blinding_in = tal(msg, struct pubkey); + *blinding_in = om->blinding->blinding; + blinding_ss = tal(msg, struct secret); + + msg = hsm_req(tmpctx, towire_hsm_ecdh_req(tmpctx, blinding_in)); + + if (!fromwire_hsm_ecdh_resp(msg, blinding_ss)) + status_failed(STATUS_FAIL_HSM_IO, + "Reading ecdh response for om blinding"); + } + + if (om->enctlv) { + const unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + u8 *dec; + struct secret rho; + int ret; + + if (!blinding_ss) { + status_debug("enctlv but no blinding?"); + return; + } + + /* We need this to decrypt enctlv */ + subkey_from_hmac("rho", blinding_ss, &rho); + + /* Overrides next_scid / next_node */ + if (tal_bytelen(om->enctlv->enctlv) + < crypto_aead_chacha20poly1305_ietf_ABYTES) { + status_debug("enctlv too short for mac"); + return; + } + + dec = tal_arr(msg, u8, + tal_bytelen(om->enctlv->enctlv) + - crypto_aead_chacha20poly1305_ietf_ABYTES); + ret = crypto_aead_chacha20poly1305_ietf_decrypt(dec, NULL, + NULL, + om->enctlv->enctlv, + tal_bytelen(om->enctlv->enctlv), + NULL, 0, + npub, + rho.data); + if (ret != 0) + errx(1, "Failed to decrypt enctlv field"); + + status_debug("enctlv -> %s", tal_hex(tmpctx, dec)); + + /* Replace onionmsg with one from enctlv */ + cursor = dec; + maxlen = tal_bytelen(dec); + + om = tlv_onionmsg_payload_new(msg); + if (!fromwire_onionmsg_payload(&cursor, &maxlen, om)) { + status_debug("onion msg: invalid enctlv onionmsg_payload %s", + tal_hex(tmpctx, dec)); + return; + } + } else if (blinding_ss && rs->nextcase != ONION_END) { + status_debug("Onion had %s, but not enctlv?", + tlvs->blinding ? "blinding" : "om blinding"); + return; + } + if (om->next_short_channel_id) next_scid = &om->next_short_channel_id->short_channel_id; else @@ -1709,13 +1834,24 @@ static void handle_onion_message(struct peer *peer, const u8 *msg) } if (rs->nextcase == ONION_END) { - /* FIXME: Handle reply_path */ - /* payload may not be valid, so we hand it pre-decrypted to lightningd */ + struct pubkey *blinding; + const struct onionmsg_path **path; + + if (om->reply_path) { + blinding = &om->reply_path->blinding; + path = cast_const2(const struct onionmsg_path **, + om->reply_path->path); + } else { + blinding = NULL; + path = NULL; + } wire_sync_write(MASTER_FD, take(towire_got_onionmsg_to_us(NULL, - NULL, - NULL))); + blinding, + path))); } else { + struct pubkey *next_blinding; + /* This *MUST* have instructions on where to go next. */ if (!next_scid && !next_node) { status_debug("onion msg: no next field in %s", @@ -1723,11 +1859,19 @@ static void handle_onion_message(struct peer *peer, const u8 *msg) return; } + if (blinding_ss) { + /* E(i-1) = H(E(i) || ss(i)) * E(i) */ + struct sha256 h = hash_e_and_ss(blinding_in, blinding_ss); + next_blinding = tal(msg, struct pubkey); + *next_blinding = next_pubkey(blinding_in, &h); + } else + next_blinding = NULL; + wire_sync_write(MASTER_FD, take(towire_got_onionmsg_forward(NULL, next_scid, next_node, - NULL, + next_blinding, serialize_onionpacket(tmpctx, rs->next)))); } } diff --git a/tests/test_misc.py b/tests/test_misc.py index aecd96ae4c2f..2e08cee120a7 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -2182,6 +2182,8 @@ def test_sendcustommsg(node_factory): def test_sendonionmessage(node_factory): l1, l2, l3 = node_factory.line_graph(3) + blindedpathtool = os.path.join(os.path.dirname(__file__), "..", "devtools", "blindedpath") + l1.rpc.call('sendonionmessage', {'hops': [{'id': l2.info['id']}, @@ -2196,7 +2198,23 @@ def test_sendonionmessage(node_factory): {'id': l3.info['id']}]}) assert l3.daemon.wait_for_log('Got onionmsg') - # FIXME: Test blinded path! + # Now test blinded path. + output = subprocess.check_output( + [blindedpathtool, '--simple-output', 'create', l2.info['id'], l3.info['id']] + ).decode('ASCII').strip() + + # First line is blinding, then then . + blinding, p1, p1enc, p2 = output.split('\n') + # First hop can't be blinded! + assert p1 == l2.info['id'] + + l1.rpc.call('sendonionmessage', + {'hops': + [{'id': l2.info['id'], + 'blinding': blinding, + 'enctlv': p1enc}, + {'id': p2}]}) + assert l3.daemon.wait_for_log('Got onionmsg') @unittest.skipIf(not DEVELOPER, "needs --dev-force-privkey") From 587a18195bb831b56461e98fd12e90141c5380d0 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 1 Apr 2020 14:23:22 +1030 Subject: [PATCH 10/11] plugins: expose onion_message hook (EXPERIMENTAL_FEATURES) Signed-off-by: Rusty Russell --- lightningd/onion_message.c | 74 ++++++++++++++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 7 deletions(-) diff --git a/lightningd/onion_message.c b/lightningd/onion_message.c index 7025ad0c5fdd..efb7c303c38e 100644 --- a/lightningd/onion_message.c +++ b/lightningd/onion_message.c @@ -4,9 +4,59 @@ #include #include #include +#include #include #if EXPERIMENTAL_FEATURES +struct onion_message_hook_payload { + /* Optional */ + struct pubkey *reply_blinding; + struct onionmsg_path **reply_path; + + /* FIXME: Include other TLV fields here! */ +}; + +static void +onion_message_serialize(struct onion_message_hook_payload *payload, + struct json_stream *stream) +{ + json_object_start(stream, "onion_message"); + if (payload->reply_path) { + json_array_start(stream, "reply_path"); + for (size_t i = 0; i < tal_count(payload->reply_path); i++) { + json_object_start(stream, NULL); + json_add_pubkey(stream, "id", + &payload->reply_path[i]->node_id); + if (payload->reply_path[i]->enctlv) + json_add_hex_talarr(stream, "enctlv", + payload->reply_path[i]->enctlv); + if (i == 0) + json_add_pubkey(stream, "blinding", + payload->reply_blinding); + json_object_end(stream); + } + json_array_end(stream); + } + json_object_end(stream); +} + +static void +onion_message_hook_cb(struct onion_message_hook_payload *payload, + const char *buffer, + const jsmntok_t *toks) +{ + /* The core infra checks the "result"; anything other than continue + * just stops. */ + tal_free(payload); +} + +REGISTER_PLUGIN_HOOK(onion_message, + PLUGIN_HOOK_CHAIN, + onion_message_hook_cb, + struct onion_message_hook_payload *, + onion_message_serialize, + struct onion_message_hook_payload *); + /* Returns false if we can't tell it */ static bool make_peer_send(struct lightningd *ld, struct channel *dst, const u8 *msg TAKES) @@ -40,19 +90,29 @@ static bool make_peer_send(struct lightningd *ld, void handle_onionmsg_to_us(struct channel *channel, const u8 *msg) { - struct pubkey *reply_blinding; - struct onionmsg_path **reply_path; + struct lightningd *ld = channel->peer->ld; + struct onion_message_hook_payload *payload; + + payload = tal(ld, struct onion_message_hook_payload); - if (!fromwire_got_onionmsg_to_us(msg, msg, - &reply_blinding, &reply_path)) { + if (!fromwire_got_onionmsg_to_us(payload, msg, + &payload->reply_blinding, + &payload->reply_path)) { channel_internal_error(channel, "bad got_onionmsg_tous: %s", tal_hex(tmpctx, msg)); return; } - log_info(channel->log, "Got onionmsg%s%s", - reply_blinding ? " reply_blinding": "", - reply_path ? " reply_path": ""); + if (payload->reply_path && !payload->reply_blinding) { + log_broken(channel->log, + "No reply blinding, ignoring reply path"); + payload->reply_path = tal_free(payload->reply_path); + } + + log_debug(channel->log, "Got onionmsg%s%s", + payload->reply_blinding ? " reply_blinding": "", + payload->reply_path ? " reply_path": ""); + plugin_hook_call_onion_message(ld, payload, payload); } void handle_onionmsg_forward(struct channel *channel, const u8 *msg) From edd02305843e4cbc49c76334d3c55ce477e125a5 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 1 Apr 2020 14:23:22 +1030 Subject: [PATCH 11/11] pytest: test the reply functionality (via blinded path) using a plugin. Signed-off-by: Rusty Russell --- tests/plugins/onionmessage-reply.py | 21 ++++++++++++++++++++ tests/test_misc.py | 30 +++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100755 tests/plugins/onionmessage-reply.py diff --git a/tests/plugins/onionmessage-reply.py b/tests/plugins/onionmessage-reply.py new file mode 100755 index 000000000000..66ad0611c84a --- /dev/null +++ b/tests/plugins/onionmessage-reply.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +""" +This plugin is used to test the `onion_message` hook. +""" +from lightning import Plugin + +plugin = Plugin() + + +@plugin.hook("onion_message") +def on_onion_message(plugin, onion_message, **kwargs): + if 'reply_path' not in onion_message: + plugin.log("no reply path") + return + + plugin.rpc.call('sendonionmessage', [onion_message['reply_path']]) + plugin.log("Sent reply via {}".format(onion_message['reply_path'])) + return {"result": "continue"} + + +plugin.run() diff --git a/tests/test_misc.py b/tests/test_misc.py index 2e08cee120a7..66eba7485565 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -2217,6 +2217,36 @@ def test_sendonionmessage(node_factory): assert l3.daemon.wait_for_log('Got onionmsg') +@unittest.skipIf(not EXPERIMENTAL_FEATURES, "Needs sendonionmessage") +def test_sendonionmessage_reply(node_factory): + blindedpathtool = os.path.join(os.path.dirname(__file__), "..", "devtools", "blindedpath") + + plugin = os.path.join(os.path.dirname(__file__), "plugins", "onionmessage-reply.py") + l1, l2, l3 = node_factory.line_graph(3, opts={'plugin': plugin}) + + # Make reply path + output = subprocess.check_output( + [blindedpathtool, '--simple-output', 'create', l2.info['id'], l1.info['id']] + ).decode('ASCII').strip() + + # First line is blinding, then then . + blinding, p1, p1enc, p2 = output.split('\n') + # First hop can't be blinded! + assert p1 == l2.info['id'] + + l1.rpc.call('sendonionmessage', + {'hops': + [{'id': l2.info['id']}, + {'id': l3.info['id']}], + 'reply_path': + {'blinding': blinding, + 'path': [{'id': p1, 'enctlv': p1enc}, {'id': p2}]}}) + + assert l3.daemon.wait_for_log('Got onionmsg reply_blinding reply_path') + assert l3.daemon.wait_for_log('Sent reply via') + assert l1.daemon.wait_for_log('Got onionmsg') + + @unittest.skipIf(not DEVELOPER, "needs --dev-force-privkey") def test_getsharedsecret(node_factory): """