Skip to content

Commit

Permalink
common: make sphinx code ignorant of payload format.
Browse files Browse the repository at this point in the history
Now "raw_payload" is always the complete string (including realm or length
bytes at the front).

This has several effects:
1. We can receive an decrypt an onion which is grossly malformed.
2. We can still hand this to the htlc_accepted hook.
3. We then fail it unless the htlc_accepted accepts it manually.
4. The createonion API now takes the raw payload, and does not know
   anything about "style".

The only caveat is that the sphinx code needs to know the payload
length: we have a call for that, which simply tells it to copy the
entire onion (and treat us as the final node) if it's invalid.

Signed-off-by: Rusty Russell <[email protected]>
  • Loading branch information
rustyrussell authored and cdecker committed Dec 9, 2019
1 parent bb538a1 commit f7ebbb2
Show file tree
Hide file tree
Showing 17 changed files with 543 additions and 512 deletions.
1 change: 1 addition & 0 deletions channeld/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ CHANNELD_COMMON_OBJS := \
common/memleak.o \
common/msg_queue.o \
common/node_id.o \
common/onion.o \
common/peer_billboard.o \
common/peer_failed.o \
common/per_peer_state.o \
Expand Down
1 change: 1 addition & 0 deletions common/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ COMMON_SRC_NOGEN := \
common/memleak.c \
common/msg_queue.c \
common/node_id.c \
common/onion.c \
common/param.c \
common/per_peer_state.c \
common/peer_billboard.c \
Expand Down
20 changes: 3 additions & 17 deletions common/json_tok.c
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ struct command_result *param_hops_array(struct command *cmd, const char *name,
const char *buffer, const jsmntok_t *tok,
struct sphinx_hop **hops)
{
const jsmntok_t *hop, *payloadtok, *styletok, *pubkeytok;
const jsmntok_t *hop, *payloadtok, *pubkeytok;
struct sphinx_hop h;
size_t i;
if (tok->type != JSMN_ARRAY) {
Expand All @@ -274,9 +274,7 @@ struct command_result *param_hops_array(struct command *cmd, const char *name,
*hops = tal_arr(cmd, struct sphinx_hop, 0);

json_for_each_arr(i, hop, tok) {

payloadtok = json_get_member(buffer, hop, "payload");
styletok = json_get_member(buffer, hop, "style");
pubkeytok = json_get_member(buffer, hop, "pubkey");

if (!pubkeytok)
Expand All @@ -287,33 +285,21 @@ struct command_result *param_hops_array(struct command *cmd, const char *name,
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Hop %zu does not have a payload", i);

h.payload = json_tok_bin_from_hex(*hops, buffer, payloadtok);
h.raw_payload = json_tok_bin_from_hex(*hops, buffer, payloadtok);
if (!json_to_pubkey(buffer, pubkeytok, &h.pubkey))
return command_fail(
cmd, JSONRPC2_INVALID_PARAMS,
"'pubkey' should be a pubkey, not '%.*s'",
pubkeytok->end - pubkeytok->start,
buffer + pubkeytok->start);

if (!h.payload)
if (!h.raw_payload)
return command_fail(
cmd, JSONRPC2_INVALID_PARAMS,
"'payload' should be a hex encoded binary, not '%.*s'",
pubkeytok->end - pubkeytok->start,
buffer + pubkeytok->start);

if (!styletok || json_tok_streq(buffer, styletok, "tlv")) {
h.type = SPHINX_TLV_PAYLOAD;
} else if (json_tok_streq(buffer, styletok, "legacy")) {
h.type = SPHINX_V0_PAYLOAD;
} else {
return command_fail(
cmd, JSONRPC2_INVALID_PARAMS,
"Unknown payload type for hop %zu: '%.*s'", i,
pubkeytok->end - pubkeytok->start,
buffer + pubkeytok->start);
}

tal_arr_expand(hops, h);
}

Expand Down
308 changes: 308 additions & 0 deletions common/onion.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
#include "common/onion.h"
#include <assert.h>
#include <ccan/array_size/array_size.h>
#include <common/sphinx.h>
#include <wire/gen_onion_wire.h>

/* BOLT #4:
*
* ## Legacy `hop_data` payload format
*
* The `hop_data` format is identified by a single `0x00`-byte length,
* for backward compatibility. Its payload is defined as:
*
* 1. type: `hop_data` (for `realm` 0)
* 2. data:
* * [`short_channel_id`:`short_channel_id`]
* * [`u64`:`amt_to_forward`]
* * [`u32`:`outgoing_cltv_value`]
* * [`12*byte`:`padding`]
*/
static u8 *make_v0_hop(const tal_t *ctx,
const struct short_channel_id *scid,
struct amount_msat forward, u32 outgoing_cltv)
{
const u8 padding[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
/* Prepend 0 byte for realm */
u8 *buf = tal_arrz(ctx, u8, 1);
towire_short_channel_id(&buf, scid);
towire_u64(&buf, forward.millisatoshis); /* Raw: low-level serializer */
towire_u32(&buf, outgoing_cltv);
towire(&buf, padding, ARRAY_SIZE(padding));
assert(tal_bytelen(buf) == 1 + 32);
return buf;
}

static u8 *make_tlv_hop(const tal_t *ctx,
const struct tlv_tlv_payload *tlv)
{
/* We can't have over 64k anyway */
u8 *tlvs = tal_arr(ctx, u8, 3);

towire_tlv_payload(&tlvs, tlv);

switch (bigsize_put(tlvs, tal_bytelen(tlvs) - 3)) {
case 1:
/* Move over two unused bytes */
memmove(tlvs + 1, tlvs + 3, tal_bytelen(tlvs) - 3);
tal_resize(&tlvs, tal_bytelen(tlvs) - 2);
return tlvs;
case 3:
return tlvs;
}
abort();
}

u8 *onion_nonfinal_hop(const tal_t *ctx,
bool use_tlv,
const struct short_channel_id *scid,
struct amount_msat forward,
u32 outgoing_cltv)
{
if (use_tlv) {
struct tlv_tlv_payload *tlv = tlv_tlv_payload_new(tmpctx);
struct tlv_tlv_payload_amt_to_forward tlv_amt;
struct tlv_tlv_payload_outgoing_cltv_value tlv_cltv;
struct tlv_tlv_payload_short_channel_id tlv_scid;

/* BOLT #4:
*
* The writer:
* - MUST include `amt_to_forward` and `outgoing_cltv_value`
* for every node.
* - MUST include `short_channel_id` for every non-final node.
*/
tlv_amt.amt_to_forward = forward.millisatoshis; /* Raw: TLV convert */
tlv_cltv.outgoing_cltv_value = outgoing_cltv;
tlv_scid.short_channel_id = *scid;
tlv->amt_to_forward = &tlv_amt;
tlv->outgoing_cltv_value = &tlv_cltv;
tlv->short_channel_id = &tlv_scid;

return make_tlv_hop(ctx, tlv);
} else {
return make_v0_hop(ctx, scid, forward, outgoing_cltv);
}
}

u8 *onion_final_hop(const tal_t *ctx,
bool use_tlv,
struct amount_msat forward,
u32 outgoing_cltv,
struct amount_msat total_msat,
const struct secret *payment_secret)
{
/* These go together! */
if (!payment_secret)
assert(amount_msat_eq(total_msat, forward));

if (use_tlv) {
struct tlv_tlv_payload *tlv = tlv_tlv_payload_new(tmpctx);
struct tlv_tlv_payload_amt_to_forward tlv_amt;
struct tlv_tlv_payload_outgoing_cltv_value tlv_cltv;
#if EXPERIMENTAL_FEATURES
struct tlv_tlv_payload_payment_data tlv_pdata;
#endif

/* BOLT #4:
*
* The writer:
* - MUST include `amt_to_forward` and `outgoing_cltv_value`
* for every node.
*...
* - MUST NOT include `short_channel_id` for the final node.
*/
tlv_amt.amt_to_forward = forward.millisatoshis; /* Raw: TLV convert */
tlv_cltv.outgoing_cltv_value = outgoing_cltv;
tlv->amt_to_forward = &tlv_amt;
tlv->outgoing_cltv_value = &tlv_cltv;

#if EXPERIMENTAL_FEATURES
if (payment_secret) {
tlv_pdata.payment_secret = *payment_secret;
tlv_pdata.total_msat = total_msat.millisatoshis; /* Raw: TLV convert */
tlv->payment_data = &tlv_pdata;
}
#else
/* Wihtout EXPERIMENTAL_FEATURES, we can't send payment_secret */
if (payment_secret)
return NULL;
#endif
return make_tlv_hop(ctx, tlv);
} else {
static struct short_channel_id all_zero_scid;
/* No payment secrets in legacy format. */
if (payment_secret)
return NULL;
return make_v0_hop(ctx, &all_zero_scid, forward, outgoing_cltv);
}
}

/* Returns true if valid, and fills in type. */
static bool pull_payload_length(const u8 **cursor,
size_t *max,
enum onion_payload_type *type,
size_t *len)
{
/* *len will incorporate bytes we read from cursor */
const u8 *start = *cursor;

/* BOLT #4:
*
* The `length` field determines both the length and the format of the
* `hop_payload` field; the following formats are defined:
*/
*len = fromwire_bigsize(cursor, max);
if (!cursor)
return false;

/* BOLT #4:
* - Legacy `hop_data` format, identified by a single `0x00` byte for
* length. In this case the `hop_payload_length` is defined to be 32
* bytes.
*/
if (*len == 0) {
if (type)
*type = ONION_V0_PAYLOAD;
assert(*cursor - start == 1);
*len = 1 + 32;
return true;
}

#if !EXPERIMENTAL_FEATURES
/* Only handle legacy format */
return false;
#else
/* BOLT #4:
* - `tlv_payload` format, identified by any length over `1`. In this
* case the `hop_payload_length` is equal to the numeric value of
* `length`.
*/
if (*len > 1) {
/* It's still invalid if it claims to be too long! */
if (*len > ROUTING_INFO_SIZE - HMAC_SIZE)
return false;

if (type)
*type = ONION_TLV_PAYLOAD;
*len += (*cursor - start);
return true;
}

return false;
#endif /* EXPERIMENTAL_FEATURES */
}

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

/* If it's not valid, copy the entire thing. */
if (!*valid)
return len;

return payload_len;
}

struct onion_payload *onion_decode(const tal_t *ctx,
const struct route_step *rs)
{
struct onion_payload *p = tal(ctx, struct onion_payload);
const u8 *cursor = rs->raw_payload;
size_t max = tal_bytelen(cursor), len;
struct tlv_tlv_payload *tlv;

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

switch (p->type) {
case ONION_V0_PAYLOAD:
p->type = ONION_V0_PAYLOAD;
p->forward_channel = tal(p, struct short_channel_id);
fromwire_short_channel_id(&cursor, &max, p->forward_channel);
p->amt_to_forward = fromwire_amount_msat(&cursor, &max);
p->outgoing_cltv = fromwire_u32(&cursor, &max);
p->payment_secret = NULL;

if (rs->nextcase == ONION_FORWARD) {
p->total_msat = NULL;
} else {
/* BOLT-e36f7b6517e1173dcbd49da3b516cfe1f48ae556 #4:
* - if it is the final node:
* - MUST treat `total_msat` as if it were equal to
* `amt_to_forward` if it is not present. */
p->total_msat = tal_dup(p, struct amount_msat,
&p->amt_to_forward);
}

/* If they somehow got an invalid onion this far, fail. */
if (!cursor)
return tal_free(p);
return p;

case ONION_TLV_PAYLOAD:
tlv = tlv_tlv_payload_new(p);
if (!fromwire_tlv_payload(&cursor, &max, tlv))
return tal_free(p);
if (!tlv_payload_is_valid(tlv, NULL))
return tal_free(p);

/* BOLT #4:
*
* The reader:
* - MUST return an error if `amt_to_forward` or
* `outgoing_cltv_value` are not present.
*/
if (!tlv->amt_to_forward || !tlv->outgoing_cltv_value)
return tal_free(p);

amount_msat_from_u64(&p->amt_to_forward,
tlv->amt_to_forward->amt_to_forward);
p->outgoing_cltv = tlv->outgoing_cltv_value->outgoing_cltv_value;

/* BOLT #4:
*
* The writer:
*...
* - MUST include `short_channel_id` for every non-final node.
*/
if (rs->nextcase == ONION_FORWARD) {
if (!tlv->short_channel_id)
return tal_free(p);
p->forward_channel = tal(p, struct short_channel_id);
*p->forward_channel
= tlv->short_channel_id->short_channel_id;
p->total_msat = NULL;
} else {
p->forward_channel = NULL;
/* BOLT-e36f7b6517e1173dcbd49da3b516cfe1f48ae556 #4:
* - if it is the final node:
* - MUST treat `total_msat` as if it were equal to
* `amt_to_forward` if it is not present. */
p->total_msat = tal_dup(p, struct amount_msat,
&p->amt_to_forward);
}

p->payment_secret = NULL;

#if EXPERIMENTAL_FEATURES
if (tlv->payment_data) {
p->payment_secret = tal_dup(p, struct secret,
&tlv->payment_data->payment_secret);
tal_free(p->total_msat);
p->total_msat = tal(p, struct amount_msat);
p->total_msat->millisatoshis /* Raw: tu64 on wire */
= tlv->payment_data->total_msat;
}
#endif
tal_free(tlv);
return p;
}

/* You said it was a valid type! */
abort();
}
Loading

0 comments on commit f7ebbb2

Please sign in to comment.