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

Onion TLV support (EXPERIMENTAL_FEATURE) #3167

Merged
merged 10 commits into from
Nov 14, 2019
Merged
3 changes: 3 additions & 0 deletions common/features.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ static const u32 our_features[] = {
OPTIONAL_FEATURE(OPT_DATA_LOSS_PROTECT),
OPTIONAL_FEATURE(OPT_UPFRONT_SHUTDOWN_SCRIPT),
OPTIONAL_FEATURE(OPT_GOSSIP_QUERIES),
#if EXPERIMENTAL_FEATURES
OPTIONAL_FEATURE(OPT_VAR_ONION),
#endif
OPTIONAL_FEATURE(OPT_GOSSIP_QUERIES_EX),
OPTIONAL_FEATURE(OPT_STATIC_REMOTEKEY),
};
Expand Down
185 changes: 179 additions & 6 deletions common/sphinx.c
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,10 @@ void sphinx_add_raw_hop(struct sphinx_path *path, const struct pubkey *pubkey,
assert(sphinx_path_payloads_size(path) <= ROUTING_INFO_SIZE);
}

void sphinx_add_v0_hop(struct sphinx_path *path, const struct pubkey *pubkey,
const struct short_channel_id *scid,
struct amount_msat forward, u32 outgoing_cltv)
static void sphinx_add_v0_hop(struct sphinx_path *path,
const struct pubkey *pubkey,
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};
Expand All @@ -149,6 +150,80 @@ void sphinx_add_v0_hop(struct sphinx_path *path, const struct pubkey *pubkey,
sphinx_add_raw_hop(path, pubkey, 0, buf);
}

static void sphinx_add_tlv_hop(struct sphinx_path *path,
const struct pubkey *pubkey,
const struct tlv_tlv_payload *tlv)
{
u8 *tlvs = tal_arr(path, u8, 0);
towire_tlvs(&tlvs, tlvs_tlv_payload, TLVS_TLV_PAYLOAD_ARRAY_SIZE, tlv);
sphinx_add_raw_hop(path, pubkey, tal_bytelen(tlvs), tlvs);
}

void sphinx_add_nonfinal_hop(struct sphinx_path *path,
const struct pubkey *pubkey,
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;

sphinx_add_tlv_hop(path, pubkey, tlv);
} else {
sphinx_add_v0_hop(path, pubkey, scid, forward, outgoing_cltv);
}
}

void sphinx_add_final_hop(struct sphinx_path *path,
const struct pubkey *pubkey,
bool use_tlv,
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;

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

sphinx_add_tlv_hop(path, pubkey, tlv);
} else {
static struct short_channel_id all_zero_scid;
sphinx_add_v0_hop(path, pubkey, &all_zero_scid,
forward, outgoing_cltv);
}
}

/* Small helper to append data to a buffer and update the position
* into the buffer
*/
Expand Down Expand Up @@ -426,7 +501,7 @@ static struct hop_params *generate_hop_params(
return params;
}

static void deserialize_hop_data(struct hop_data *data, const u8 *src)
static void deserialize_hop_data(struct hop_data_legacy *data, const u8 *src)
{
const u8 *cursor = src;
size_t max = FRAME_SIZE;
Expand Down Expand Up @@ -477,16 +552,31 @@ static void sphinx_parse_payload(struct route_step *step, const u8 *src)
}
#endif

/* Legacy hop_data support */
/* BOLT #4:
*
* The `length` field determines both the length and the format of the
* `hop_payload` field; the following formats are defined:
*
* - 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.
*
* - `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 (src[0] == 0x00) {
vsize = 1;
raw_size = 32;
hop_size = FRAME_SIZE;
step->type = SPHINX_V0_PAYLOAD;
} else {
} else if (src[0] > 1) {
vsize = bigsize_get(src, 3, &raw_size);
hop_size = raw_size + vsize + HMAC_SIZE;
step->type = SPHINX_TLV_PAYLOAD;
} else {
step->type = SPHINX_INVALID_PAYLOAD;
return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, the src[0] == 1 escape hatch got lost somewhere before.

}

/* Copy common pieces over */
Expand All @@ -497,6 +587,18 @@ static void sphinx_parse_payload(struct route_step *step, const u8 *src)
* later. */
if (step->type == SPHINX_V0_PAYLOAD)
deserialize_hop_data(&step->payload.v0, src);
else if (step->type == SPHINX_TLV_PAYLOAD) {
const u8 *tlv = step->raw_payload;
size_t max = tal_bytelen(tlv);
step->payload.tlv = tlv_tlv_payload_new(step);
if (!fromwire_tlvs(&tlv, &max, tlvs_tlv_payload,
TLVS_TLV_PAYLOAD_ARRAY_SIZE,
step->payload.tlv)) {
/* FIXME: record offset of violation for error! */
step->type = SPHINX_INVALID_PAYLOAD;
return;
}
}
}

struct onionpacket *create_onionpacket(
Expand Down Expand Up @@ -781,3 +883,74 @@ struct onionreply *unwrap_onionreply(const tal_t *ctx,
return oreply;

}

/**
* Helper to extract fields from ONION_END.
*/
bool route_step_decode_end(const struct route_step *rs,
struct amount_msat *amt_forward,
u32 *outgoing_cltv)
{
assert(rs->nextcase == ONION_END);

switch (rs->type) {
case SPHINX_V0_PAYLOAD:
*amt_forward = rs->payload.v0.amt_forward;
*outgoing_cltv = rs->payload.v0.outgoing_cltv;
return true;
case SPHINX_TLV_PAYLOAD:
if (!rs->payload.tlv->amt_to_forward)
return false;
if (!rs->payload.tlv->outgoing_cltv_value)
rustyrussell marked this conversation as resolved.
Show resolved Hide resolved
return false;
amt_forward->millisatoshis /* Raw: tu64 -> millisatoshis */
= rs->payload.tlv->amt_to_forward->amt_to_forward;
*outgoing_cltv = rs->payload.tlv->outgoing_cltv_value->outgoing_cltv_value;
return true;
case SPHINX_INVALID_PAYLOAD:
return false;

/* This should probably be removed, as it's just for testing */
case SPHINX_RAW_PAYLOAD:
abort();
}
abort();
Comment on lines +913 to +917
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, if you want I can clean it up since it also touches the onion-tool.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The onion tool actually uses this to make an arbitrary raw payload, so we'll leave it for a bit longer.

}

/**
* Helper to extract fields from ONION_FORWARD.
*/
bool route_step_decode_forward(const struct route_step *rs,
struct amount_msat *amt_forward,
u32 *outgoing_cltv,
struct short_channel_id *scid)
{
assert(rs->nextcase == ONION_FORWARD);

switch (rs->type) {
case SPHINX_V0_PAYLOAD:
*amt_forward = rs->payload.v0.amt_forward;
*outgoing_cltv = rs->payload.v0.outgoing_cltv;
*scid = rs->payload.v0.channel_id;
return true;
case SPHINX_TLV_PAYLOAD:
if (!rs->payload.tlv->amt_to_forward)
return false;
amt_forward->millisatoshis /* Raw: tu64 -> millisatoshis */
= rs->payload.tlv->amt_to_forward->amt_to_forward;
if (!rs->payload.tlv->outgoing_cltv_value)
return false;
*outgoing_cltv = rs->payload.tlv->outgoing_cltv_value->outgoing_cltv_value;
if (!rs->payload.tlv->short_channel_id)
return false;
*scid = rs->payload.tlv->short_channel_id->short_channel_id;
return true;
case SPHINX_INVALID_PAYLOAD:
return false;

/* This should probably be removed, as it's just for testing */
case SPHINX_RAW_PAYLOAD:
abort();
}
abort();
}
44 changes: 36 additions & 8 deletions common/sphinx.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ struct sphinx_path;
* * [`u32`:`outgoing_cltv_value`]
* * [`12*byte`:`padding`]
*/
struct hop_data {
struct hop_data_legacy {
u8 realm;
struct short_channel_id channel_id;
struct amount_msat amt_forward;
Expand All @@ -80,7 +80,8 @@ struct route_step {
struct onionpacket *next;
enum sphinx_payload_type type;
union {
struct hop_data v0;
struct hop_data_legacy v0;
struct tlv_tlv_payload *tlv;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not make this an instance of the struct tlv_tlv_payload instead of a pointer to it? The struct itself only consists of 3 pointers (fixed size) and we'd be saving 1 allocation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our marshaling/unmarshaling really wants a pointer...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, ok, I should read the TLV implementation more carefully :-)

} payload;
u8 *raw_payload;
};
Expand Down Expand Up @@ -218,16 +219,43 @@ struct sphinx_path *sphinx_path_new_with_key(const tal_t *ctx,
const u8 *associated_data,
const struct secret *session_key);

/**
* Add a V0 (Realm 0) single frame hop to the path.
*/
void sphinx_add_v0_hop(struct sphinx_path *path, const struct pubkey *pubkey,
const struct short_channel_id *scid, struct amount_msat forward,
u32 outgoing_cltv);
/**
* Add a raw payload hop to the path.
*/
void sphinx_add_raw_hop(struct sphinx_path *path, const struct pubkey *pubkey,
enum sphinx_payload_type type, const u8 *payload);

/**
* Add a non-final hop to the path.
*/
void sphinx_add_nonfinal_hop(struct sphinx_path *path,
const struct pubkey *pubkey,
bool use_tlv,
const struct short_channel_id *scid,
struct amount_msat forward,
u32 outgoing_cltv);

/**
* Add a final hop to the path.
*/
void sphinx_add_final_hop(struct sphinx_path *path,
const struct pubkey *pubkey,
bool use_tlv,
struct amount_msat forward,
u32 outgoing_cltv);

/**
* Helper to extract fields from ONION_END.
*/
bool route_step_decode_end(const struct route_step *rs,
struct amount_msat *amt_forward,
u32 *outgoing_cltv);

/**
* Helper to extract fields from ONION_FORWARD.
*/
bool route_step_decode_forward(const struct route_step *rs,
struct amount_msat *amt_forward,
u32 *outgoing_cltv,
struct short_channel_id *scid);
#endif /* LIGHTNING_COMMON_SPHINX_H */
3 changes: 3 additions & 0 deletions common/test/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ $(COMMON_TEST_OBJS): $(COMMON_HEADERS) $(WIRE_HEADERS) $(COMMON_SRC)
ALL_TEST_PROGRAMS += $(COMMON_TEST_PROGRAMS)
ALL_OBJS += $(COMMON_TEST_PROGRAMS:=.o)

# Sphinx test wants to decode TLVs.
common/test/run-sphinx: wire/gen_onion_wire.o

update-mocks: $(COMMON_TEST_SRC:%=update-mocks/%)

check-units: $(COMMON_TEST_PROGRAMS:%=unittest/%)
Loading