Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental onion message support #3573

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
523d444
wire: add fromwire_tal_bytes() helper.
rustyrussell Mar 4, 2020
0cf5ac6
pytest: Add tests for the sphinx onion generation and processing
cdecker Feb 28, 2020
aff6c01
sphinx: Working onion wrapping with filler cancellation
cdecker Feb 20, 2020
9ab41b9
sphinx: Functions to enable RV mode and serialize compressed onions
cdecker Feb 28, 2020
4049d9a
onion: Allow devtool/onion to generate rendezvous onions
cdecker Feb 28, 2020
3070cf6
pytest: Add test for compressed onion
cdecker Feb 28, 2020
6296915
sphinx: Add functions to decompress
cdecker Feb 28, 2020
d829cc5
sphinx: Expose the shared secret creation function
cdecker Mar 2, 2020
1441923
sphinx: Treat compressed onions as a standalone struct
cdecker Mar 2, 2020
860abda
sphinx: Kill read_buffer with fire 🔥
cdecker Mar 3, 2020
75ef940
sphinx: Migrate sphinx compression to new interface
cdecker Mar 3, 2020
71b9da2
sphinx: Use fromwire_tal_bytes() to deserialize compressed onions
cdecker Mar 4, 2020
e7dacae
devtools/onion: rename --rendezvous-id to --partial-to
rustyrussell Mar 6, 2020
e8bfc2e
devtools/onion: allow '-' input file so you can pipe from stdin.
rustyrussell Mar 6, 2020
f33b88a
devtools/onion: change defile assocdata to empty.
rustyrussell Mar 6, 2020
1267d97
tests: note the private keys of our test nodes.
rustyrussell Mar 6, 2020
79ed54f
sphinx: add (unused) infrastructure for end-to-end payload.
rustyrussell Mar 6, 2020
c224cca
common/sphinx: add realm flag so we can avoid legacy parsing.
rustyrussell Mar 6, 2020
0163b69
devtools/onion: print shared secrets.
rustyrussell Mar 6, 2020
98c5a43
devtools/onionmessage: new tool for encrypting/decrypting payloads.
rustyrussell Mar 6, 2020
c74944e
channeld: messages to handle 'onion message' EXPERIMENTAL_FEATURES
rustyrussell Mar 6, 2020
3be02b4
lightningd: forward onion messages.
rustyrussell Mar 6, 2020
676bcc8
plugins: expose onion_message hook (EXPERIMENTAL_FEATURES)
rustyrussell Mar 6, 2020
dd2873f
lightningd: EXPERIMENTAL_FEATURES JSON command to sendonionmessage.
rustyrussell Mar 6, 2020
721c8b7
pytest: test the reply functionality using a plugin.
rustyrussell Mar 6, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions channeld/channel_wire.csv
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <common/channel_config.h>
#include <common/derive_basepoints.h>
#include <common/fee_states.h>
#include <common/onionreply.h>
#include <common/per_peer_state.h>

# Begin! (passes gossipd-client fd)
Expand Down Expand Up @@ -206,3 +207,26 @@ 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,next_scid,?short_channel_id,
msgdata,got_onionmsg_to_us,next_node_id,?node_id,
msgdata,got_onionmsg_to_us,next_onion_len,u16,
msgdata,got_onionmsg_to_us,next_onion,u8,next_onion_len
msgdata,got_onionmsg_to_us,shared_secret,secret,
msgdata,got_onionmsg_to_us,e2e_len,u16,
msgdata,got_onionmsg_to_us,e2e_payload,u8,e2e_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,onion,u8,1366
msgdata,got_onionmsg_forward,len,u16,
msgdata,got_onionmsg_forward,e2e_payload,u8,len

# Lightnignd tells us to send a onion message.
msgtype,send_onionmsg,1040
msgdata,send_onionmsg,onion,u8,1366
msgdata,send_onionmsg,len,u16,
msgdata,send_onionmsg,e2e_payload,u8,len
177 changes: 177 additions & 0 deletions channeld/channeld.c
Original file line number Diff line number Diff line change
Expand Up @@ -1622,6 +1622,167 @@ static bool channeld_handle_custommsg(const u8 *msg)
#endif
}

#if EXPERIMENTAL_FEATURES
static u8 *unpack_onion(const tal_t *ctx, const u8 *partial_onion)
{
struct secret ss;
struct sphinx_compressed_onion *c;
const u8 *msg;

c = sphinx_compressed_onion_deserialize(tmpctx, partial_onion);
if (!c) {
status_debug("onion msg: invalid next_onion %s",
tal_hex(tmpctx, partial_onion));
return NULL;
}

/* We need to ecdh again to get shared secret here.
* FIXME: Can't we share? */
msg = hsm_req(tmpctx, towire_hsm_ecdh_req(tmpctx, &c->ephemeralkey));
if (!fromwire_hsm_ecdh_resp(msg, &ss))
status_failed(STATUS_FAIL_HSM_IO, "Reading ecdh response");

return serialize_onionpacket(ctx, sphinx_decompress(tmpctx, c, &ss));
}

/* Peer sends onion msg. */
static void handle_onionmsg(struct peer *peer, const u8 *msg)
{
enum onion_type badreason;
struct onionpacket op;
struct secret ss;
struct route_step *rs;
struct sha256 hash;
u8 onion[TOTAL_PACKET_SIZE];
const u8 *cursor;
u8 *e2e_payload, *next_onion;
size_t max, maxlen;
struct tlv_tlv_dm_payload *dm;
const struct short_channel_id *next_scid;
struct node_id *next_node;

if (!fromwire_onionmsg(msg, msg, onion, &e2e_payload))
peer_failed(peer->pps,
&peer->channel_id,
"Bad onionmsg %s", tal_hex(peer, msg));

sha256(&hash, onion, sizeof(onion));

/* 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;
}

dm = tlv_tlv_dm_payload_new(msg);
if (!fromwire_tlv_dm_payload(&cursor, &maxlen, dm)) {
status_debug("onion msg: invalid dm_payload %s",
tal_hex(tmpctx, rs->raw_payload));
return;
}

if (dm->next_short_channel_id)
next_scid = &dm->next_short_channel_id->short_channel_id;
else
next_scid = NULL;

if (dm->next_node_id) {
next_node = tal(msg, struct node_id);
node_id_from_pubkey(next_node, &dm->next_node_id->node_id);
} else
next_node = NULL;

if (dm->forward_onion) {
/* If this has a forward onion, we use that, and forward */
next_onion = unpack_onion(msg, dm->forward_onion->partial_onion);
rs->nextcase = ONION_FORWARD;
} else
next_onion = NULL;

if (rs->nextcase == ONION_END) {
u8 *reply_onion;
if (dm->reply_onion)
reply_onion = unpack_onion(msg, dm->reply_onion->partial_onion);
else
reply_onion = NULL;

/* 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,
next_scid,
next_node,
reply_onion,
&ss,
e2e_payload)));
} else {
e2e_payload = unwrap_e2e_payload(tmpctx, e2e_payload, &ss);

/* If they don't override, we just send next onion */
if (!next_onion)
next_onion = serialize_onionpacket(tmpctx, rs->next);

/* 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,
next_onion,
e2e_payload)));
}
}

/* We send onion msg. */
static void send_onionmsg(struct peer *peer, const u8 *msg)
{
u8 onion_routing_packet[TOTAL_PACKET_SIZE];
u8 *e2e_payload;

if (!fromwire_send_onionmsg(msg, msg,
onion_routing_packet, &e2e_payload))
master_badmsg(WIRE_SEND_ONIONMSG, msg);

sync_crypto_write(peer->pps,
take(towire_onionmsg(NULL, onion_routing_packet, e2e_payload)));
}
#endif /* EXPERIMENTAL_FEATURES */

static void peer_in(struct peer *peer, const u8 *msg)
{
enum wire_type type = fromwire_peektype(msg);
Expand Down Expand Up @@ -1693,6 +1854,11 @@ static void peer_in(struct peer *peer, const u8 *msg)
case WIRE_SHUTDOWN:
handle_peer_shutdown(peer, msg);
return;
#if EXPERIMENTAL_FEATURES
case WIRE_ONIONMSG:
handle_onionmsg(peer, msg);
return;
#endif

case WIRE_INIT:
case WIRE_OPEN_CHANNEL:
Expand Down Expand Up @@ -2681,6 +2847,15 @@ 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);
Expand Down Expand Up @@ -2708,6 +2883,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;
}

Expand Down
20 changes: 13 additions & 7 deletions common/onion.c
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ u8 *onion_final_hop(const tal_t *ctx,
/* Returns true if valid, and fills in type. */
static bool pull_payload_length(const u8 **cursor,
size_t *max,
bool has_realm,
enum onion_payload_type *type,
size_t *len)
{
Expand All @@ -163,7 +164,7 @@ static bool pull_payload_length(const u8 **cursor,
* length. In this case the `hop_payload_length` is defined to be 32
* bytes.
*/
if (*len == 0) {
if (has_realm && *len == 0) {
if (type)
*type = ONION_V0_PAYLOAD;
assert(*cursor - start == 1);
Expand All @@ -176,10 +177,15 @@ static bool pull_payload_length(const u8 **cursor,
* case the `hop_payload_length` is equal to the numeric value of
* `length`.
*/
if (*len > 1) {
if (!has_realm || *len > 1) {
/* It's still invalid if it claims to be too long! */
if (*len > ROUTING_INFO_SIZE - HMAC_SIZE)
return false;
if (has_realm) {
if (*len > ROUTING_INFO_SIZE - HMAC_SIZE)
return false;
} else {
if (*len > *max)
return false;
}

if (type)
*type = ONION_TLV_PAYLOAD;
Expand All @@ -190,12 +196,12 @@ static bool pull_payload_length(const u8 **cursor,
return false;
}

size_t onion_payload_length(const u8 *raw_payload, size_t len,
size_t onion_payload_length(const u8 *raw_payload, size_t len, bool has_realm,
bool *valid,
enum onion_payload_type *type)
{
size_t max = len, payload_len;
*valid = pull_payload_length(&raw_payload, &max, type, &payload_len);
*valid = pull_payload_length(&raw_payload, &max, has_realm, type, &payload_len);

/* If it's not valid, copy the entire thing. */
if (!*valid)
Expand All @@ -214,7 +220,7 @@ struct onion_payload *onion_decode(const tal_t *ctx,
size_t max = tal_bytelen(cursor), len;
struct tlv_tlv_payload *tlv;

if (!pull_payload_length(&cursor, &max, &p->type, &len))
if (!pull_payload_length(&cursor, &max, true, &p->type, &len))
return tal_free(p);

switch (p->type) {
Expand Down
2 changes: 2 additions & 0 deletions common/onion.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ u8 *onion_final_hop(const tal_t *ctx,
* onion_payload_length: measure payload length in decrypted onion.
* @raw_payload: payload to look at.
* @len: length of @raw_payload in bytes.
* @has_realm: used for HTLCs, where first byte 0 is magical.
* @valid: set to true if it is valid, false otherwise.
* @type: if non-NULL, set to type of payload if *@valid is true.
*
Expand All @@ -47,6 +48,7 @@ u8 *onion_final_hop(const tal_t *ctx,
* the return value is @len (i.e. the entire payload).
*/
size_t onion_payload_length(const u8 *raw_payload, size_t len,
bool has_realm,
bool *valid,
enum onion_payload_type *type);

Expand Down
4 changes: 2 additions & 2 deletions common/onionreply.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ struct onionreply *fromwire_onionreply(const tal_t *ctx,
const u8 **cursor, size_t *max)
{
struct onionreply *r = tal(ctx, struct onionreply);
r->contents = tal_arr(r, u8, fromwire_u16(cursor, max));
fromwire_u8_array(cursor, max, r->contents, tal_count(r->contents));
r->contents = fromwire_tal_bytes(r, cursor, max,
fromwire_u16(cursor, max));
if (!*cursor)
return tal_free(r);
return r;
Expand Down
Loading