diff --git a/channeld/Makefile b/channeld/Makefile index 9fe4d9bd0..128e39fb1 100644 --- a/channeld/Makefile +++ b/channeld/Makefile @@ -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 \ diff --git a/common/Makefile b/common/Makefile index 1c5055af5..0a3b37a56 100644 --- a/common/Makefile +++ b/common/Makefile @@ -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 \ diff --git a/common/json_tok.c b/common/json_tok.c index 89b23b0ae..4531eac7a 100644 --- a/common/json_tok.c +++ b/common/json_tok.c @@ -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) { @@ -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) @@ -287,7 +285,7 @@ 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, @@ -295,25 +293,13 @@ struct command_result *param_hops_array(struct command *cmd, const char *name, 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); } diff --git a/common/onion.c b/common/onion.c new file mode 100644 index 000000000..1c1fe02c3 --- /dev/null +++ b/common/onion.c @@ -0,0 +1,308 @@ +#include "common/onion.h" +#include +#include +#include +#include + +/* 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(); +} diff --git a/common/onion.h b/common/onion.h new file mode 100644 index 000000000..558106330 --- /dev/null +++ b/common/onion.h @@ -0,0 +1,64 @@ +#ifndef LIGHTNING_COMMON_ONION_H +#define LIGHTNING_COMMON_ONION_H +#include "config.h" +#include +#include + +struct route_step; + +enum onion_payload_type { + ONION_V0_PAYLOAD = 0, + ONION_TLV_PAYLOAD = 1, +}; + +struct onion_payload { + enum onion_payload_type type; + + struct amount_msat amt_to_forward; + u32 outgoing_cltv; + struct amount_msat *total_msat; + struct short_channel_id *forward_channel; + struct secret *payment_secret; +}; + +u8 *onion_nonfinal_hop(const tal_t *ctx, + bool use_tlv, + const struct short_channel_id *scid, + struct amount_msat forward, + u32 outgoing_cltv); + +/* Note that this can fail if we supply payment_secret and !use_tlv! */ +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); + +/** + * onion_payload_length: measure payload length in decrypted onion. + * @raw_payload: payload to look at. + * @len: length of @raw_payload in bytes. + * @valid: set to true if it is valid, false otherwise. + * @type: if non-NULL, set to type of payload if *@valid is true. + * + * If @valid is set, there is room for the HMAC immediately following, + * as the return value is <= ROUTING_INFO_SIZE - HMAC_SIZE. Otherwise, + * the return value is @len (i.e. the entire payload). + */ +size_t onion_payload_length(const u8 *raw_payload, size_t len, + bool *valid, + enum onion_payload_type *type); + +/** + * onion_decode: decode payload from a decrypted onion. + * @ctx: context to allocate onion_contents off. + * @rs: the route_step, whose raw_payload is of at least length + * onion_payload_length(). + * + * If the payload is not valid, returns NULL. + */ +struct onion_payload *onion_decode(const tal_t *ctx, + const struct route_step *rs); + +#endif /* LIGHTNING_COMMON_ONION_H */ diff --git a/common/sphinx.c b/common/sphinx.c index 442bfe0ed..f4babdd7b 100644 --- a/common/sphinx.c +++ b/common/sphinx.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -79,29 +80,7 @@ struct sphinx_path *sphinx_path_new_with_key(const tal_t *ctx, static size_t sphinx_hop_size(const struct sphinx_hop *hop) { - size_t size = tal_bytelen(hop->payload), vsize; - - /* There is no point really in trying to serialize something that is - * larger than the maximum length we can fit into the payload region - * anyway. 3 here is the maximum bigsize size that we allow. */ - assert(size < ROUTING_INFO_SIZE - 3 - HMAC_SIZE); - - /* Backwards compatibility: realm 0 is the legacy hop_data format and - * always has 65 bytes in size */ - if (hop->type == SPHINX_V0_PAYLOAD) - return 65; - - /* Since this uses the bigsize serialization format for variable - * length integer encodings we need to allocate enough space for - * it. Values >= 0xfd are used to signal multi-byte serializations. */ - if (size < 0xFD) - vsize = 1; - else - vsize = 3; - - /* The hop must accomodate the hop_payload, as well as the bigsize - * describing the length and HMAC. */ - return vsize + size + HMAC_SIZE; + return tal_bytelen(hop->raw_payload) + HMAC_SIZE; } static size_t sphinx_path_payloads_size(const struct sphinx_path *path) @@ -112,131 +91,16 @@ static size_t sphinx_path_payloads_size(const struct sphinx_path *path) return size; } -void sphinx_add_raw_hop(struct sphinx_path *path, const struct pubkey *pubkey, - enum sphinx_payload_type type, const u8 *payload) +void sphinx_add_hop(struct sphinx_path *path, const struct pubkey *pubkey, + const u8 *payload TAKES) { struct sphinx_hop sp; - sp.payload = payload; - sp.type = type; + sp.raw_payload = tal_dup_arr(path, u8, payload, tal_count(payload), 0); sp.pubkey = *pubkey; tal_arr_expand(&path->hops, sp); assert(sphinx_path_payloads_size(path) <= ROUTING_INFO_SIZE); } -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}; - u8 *buf = tal_arr(path, u8, 0); - 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) == 32); - sphinx_add_raw_hop(path, pubkey, SPHINX_V0_PAYLOAD, 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_tlv_payload(&tlvs, tlv); - sphinx_add_raw_hop(path, pubkey, SPHINX_TLV_PAYLOAD, 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); - } -} - -bool sphinx_add_final_hop(struct sphinx_path *path, - const struct pubkey *pubkey, - 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 false; -#endif - sphinx_add_tlv_hop(path, pubkey, tlv); - } else { - static struct short_channel_id all_zero_scid; - /* No payment secrets in legacy format. */ - if (payment_secret) - return false; - sphinx_add_v0_hop(path, pubkey, &all_zero_scid, - forward, outgoing_cltv); - } - return true; -} - /* Small helper to append data to a buffer and update the position * into the buffer */ @@ -514,100 +378,10 @@ static struct hop_params *generate_hop_params( return params; } -static void deserialize_hop_data(struct hop_data_legacy *data, const u8 *src) -{ - const u8 *cursor = src; - size_t max = FRAME_SIZE; - data->realm = fromwire_u8(&cursor, &max); - fromwire_short_channel_id(&cursor, &max, &data->channel_id); - data->amt_forward = fromwire_amount_msat(&cursor, &max); - data->outgoing_cltv = fromwire_u32(&cursor, &max); -} - -static bool sphinx_write_frame(u8 *dest, const struct sphinx_hop *hop) -{ - size_t raw_size = tal_bytelen(hop->payload); - size_t hop_size = sphinx_hop_size(hop); - size_t padding_size; - int pos = 0; - - /* Backwards compatibility for the legacy hop_data format. */ - if (hop->type == SPHINX_V0_PAYLOAD) - dest[pos++] = 0x00; - else - pos += bigsize_put(dest+pos, raw_size); - - memcpy(dest + pos, hop->payload, raw_size); - pos += raw_size; - - padding_size = hop_size - pos - HMAC_SIZE; - memset(dest + pos, 0, padding_size); - pos += padding_size; - - memcpy(dest + pos, hop->hmac, HMAC_SIZE); - assert(pos + HMAC_SIZE == hop_size); - return true; -} - -static bool sphinx_parse_payload(struct route_step *step, const u8 *src) +static void sphinx_write_frame(u8 *dest, const struct sphinx_hop *hop) { - size_t hop_size, vsize; - bigsize_t raw_size; -#if !EXPERIMENTAL_FEATURES - if (src[0] != 0x00) - return false; -#endif - - /* 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 if (src[0] > 1) { - vsize = bigsize_get(src, 3, &raw_size); - hop_size = raw_size + vsize + HMAC_SIZE; - step->type = SPHINX_TLV_PAYLOAD; - } else { - return false; - } - - /* Copy common pieces over */ - step->raw_payload = tal_dup_arr(step, u8, src, raw_size + vsize, 0); - memcpy(step->next->mac, src + hop_size - HMAC_SIZE, HMAC_SIZE); - - /* And now try to parse whatever the payload contains so we can use it - * 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); - - /* The raw payload includes the length / realm prefix, Consume - * the length off of the payload, so the decoding can strat - * correctly. */ - fromwire_varint(&tlv, &max); - - if (!fromwire_tlv_payload(&tlv, &max, step->payload.tlv)) { - /* FIXME: record offset of violation for error! */ - return false; - } - } - return true; + memcpy(dest, hop->raw_payload, tal_bytelen(hop->raw_payload)); + memcpy(dest + tal_bytelen(hop->raw_payload), hop->hmac, HMAC_SIZE); } struct onionpacket *create_onionpacket( @@ -663,11 +437,7 @@ struct onionpacket *create_onionpacket( size_t shiftSize = sphinx_hop_size(&sp->hops[i]); memmove(packet->routinginfo + shiftSize, packet->routinginfo, ROUTING_INFO_SIZE-shiftSize); - if (!sphinx_write_frame(packet->routinginfo, &sp->hops[i])) { - tal_free(packet); - tal_free(secrets); - return NULL; - } + sphinx_write_frame(packet->routinginfo, &sp->hops[i]); xorbytes(packet->routinginfo, packet->routinginfo, stream, ROUTING_INFO_SIZE); if (i == num_hops - 1) { @@ -688,74 +458,6 @@ struct onionpacket *create_onionpacket( return packet; } -/** - * Helper to extract fields from the legacy or tlv payload into the top-level - * struct. - */ -static void route_step_decode(struct route_step *rs) -{ - switch (rs->type) { - case SPHINX_V0_PAYLOAD: - rs->amt_to_forward = &rs->payload.v0.amt_forward; - rs->outgoing_cltv = &rs->payload.v0.outgoing_cltv; - rs->payment_secret = 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. */ - rs->total_msat = rs->amt_to_forward; - if (rs->nextcase == ONION_FORWARD) { - rs->forward_channel = &rs->payload.v0.channel_id; - } else { - rs->forward_channel = NULL; - } - break; - case SPHINX_TLV_PAYLOAD: - if (rs->payload.tlv->amt_to_forward) { - rs->amt_to_forward = tal(rs, struct amount_msat); - amount_msat_from_u64( - rs->amt_to_forward, - rs->payload.tlv->amt_to_forward->amt_to_forward); - } else { - rs->amt_to_forward = NULL; - } - - if (rs->payload.tlv->outgoing_cltv_value) { - rs->outgoing_cltv = - &rs->payload.tlv->outgoing_cltv_value - ->outgoing_cltv_value; - } else { - rs->outgoing_cltv = NULL; - } - - if (rs->payload.tlv->short_channel_id) - rs->forward_channel = &rs->payload.tlv->short_channel_id - ->short_channel_id; - else - rs->forward_channel = NULL; - - rs->payment_secret = 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. */ - rs->total_msat = rs->amt_to_forward; - -#if EXPERIMENTAL_FEATURES - if (rs->payload.tlv->payment_data) { - rs->payment_secret - = &rs->payload.tlv->payment_data->payment_secret; - rs->total_msat = tal(rs, struct amount_msat); - rs->total_msat->millisatoshis /* Raw: tu64 on wire */ - = rs->payload.tlv->payment_data->total_msat; - } -#endif - break; - case SPHINX_RAW_PAYLOAD: - abort(); - } -} - /* * Given an onionpacket msg extract the information for the current * node and unwrap the remainder so that the node can forward it. @@ -774,8 +476,9 @@ struct route_step *process_onionpacket( u8 blind[BLINDING_FACTOR_SIZE]; u8 stream[NUM_STREAM_BYTES]; u8 paddedheader[2*ROUTING_INFO_SIZE]; - size_t vsize; + size_t payload_size; bigsize_t shift_size; + bool valid; step->next = talz(step, struct onionpacket); step->next->version = msg->version; @@ -799,28 +502,29 @@ struct route_step *process_onionpacket( if (!blind_group_element(&step->next->ephemeralkey, &msg->ephemeralkey, blind)) return tal_free(step); - if (!sphinx_parse_payload(step, paddedheader)) + payload_size = onion_payload_length(paddedheader, ROUTING_INFO_SIZE, + &valid, NULL); +#if !EXPERIMENTAL_FEATURES + /* We don't even attempt to handle non-legacy or malformed payloads */ + if (!valid) return tal_free(step); +#endif - /* Extract how many bytes we need to shift away */ - if (paddedheader[0] == 0x00) { - shift_size = FRAME_SIZE; + /* Can't decode? Treat it as terminal. */ + if (!valid) { + shift_size = payload_size; + memset(step->next->mac, 0, sizeof(step->next->mac)); } else { - /* In addition to the raw payload we need to also shift the - * length encoding itself and the HMAC away. */ - vsize = bigsize_get(paddedheader, 3, &shift_size); - shift_size += vsize + HMAC_SIZE; - - /* If we get an unreasonable shift size we must return an error. */ - if (shift_size >= ROUTING_INFO_SIZE) - return tal_free(step); + assert(payload_size <= ROUTING_INFO_SIZE - HMAC_SIZE); + /* Copy hmac */ + shift_size = payload_size + HMAC_SIZE; + memcpy(step->next->mac, paddedheader + payload_size, HMAC_SIZE); } - - /* Copy the hmac from the last HMAC_SIZE bytes */ - memcpy(&step->next->mac, paddedheader + shift_size - HMAC_SIZE, HMAC_SIZE); + step->raw_payload = tal_dup_arr(step, u8, paddedheader, payload_size, 0); /* Left shift the current payload out and make the remainder the new onion */ - memcpy(&step->next->routinginfo, paddedheader + shift_size, ROUTING_INFO_SIZE); + memcpy(&step->next->routinginfo, paddedheader + shift_size, + ROUTING_INFO_SIZE); if (memeqzero(step->next->mac, sizeof(step->next->mac))) { step->nextcase = ONION_END; @@ -828,8 +532,6 @@ struct route_step *process_onionpacket( step->nextcase = ONION_FORWARD; } - route_step_decode(step); - return step; } diff --git a/common/sphinx.h b/common/sphinx.h index df86f7652..bad91c643 100644 --- a/common/sphinx.h +++ b/common/sphinx.h @@ -68,39 +68,20 @@ struct hop_data_legacy { u32 outgoing_cltv; }; -enum sphinx_payload_type { - SPHINX_V0_PAYLOAD = 0, - SPHINX_TLV_PAYLOAD = 1, - SPHINX_RAW_PAYLOAD = 255, -}; - /* * All the necessary information to generate a valid onion for this hop on a * sphinx path. The payload is preserialized in order since the onion * generation is payload agnostic. */ struct sphinx_hop { struct pubkey pubkey; - enum sphinx_payload_type type; - const u8 *payload; + const u8 *raw_payload; u8 hmac[HMAC_SIZE]; }; struct route_step { enum route_next_case nextcase; struct onionpacket *next; - enum sphinx_payload_type type; - union { - struct hop_data_legacy v0; - struct tlv_tlv_payload *tlv; - } payload; u8 *raw_payload; - - /* Quick access for internal use. */ - struct amount_msat *amt_to_forward; - u32 *outgoing_cltv; - struct short_channel_id *forward_channel; - struct secret *payment_secret; - struct amount_msat *total_msat; }; /** @@ -237,30 +218,9 @@ struct sphinx_path *sphinx_path_new_with_key(const tal_t *ctx, const struct secret *session_key); /** - * 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. + * Add a payload hop to the path. */ -bool sphinx_add_final_hop(struct sphinx_path *path, - const struct pubkey *pubkey, - bool use_tlv, - struct amount_msat forward, - u32 outgoing_cltv, - struct amount_msat total_msat, - const struct secret *payment_secret); +void sphinx_add_hop(struct sphinx_path *path, const struct pubkey *pubkey, + const u8 *payload TAKES); #endif /* LIGHTNING_COMMON_SPHINX_H */ diff --git a/common/test/run-sphinx.c b/common/test/run-sphinx.c index a1161620a..5b4c4b1c4 100644 --- a/common/test/run-sphinx.c +++ b/common/test/run-sphinx.c @@ -1,3 +1,4 @@ +#include "../onion.c" #include "../sphinx.c" #include #include diff --git a/devtools/Makefile b/devtools/Makefile index 1127af439..e6e04c7b8 100644 --- a/devtools/Makefile +++ b/devtools/Makefile @@ -20,6 +20,7 @@ DEVTOOLS_COMMON_OBJS := \ common/hash_u5.o \ common/memleak.o \ common/node_id.o \ + common/onion.o \ common/per_peer_state.o \ common/pseudorand.o \ common/json.o \ diff --git a/devtools/onion.c b/devtools/onion.c index a9971b190..4bec8b933 100644 --- a/devtools/onion.c +++ b/devtools/onion.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -65,8 +66,7 @@ static void do_generate(int argc, char **argv, if (!data) errx(1, "bad hex after / in %s", argv[1 + i]); - sphinx_add_raw_hop(sp, &path[i], SPHINX_RAW_PAYLOAD, - data); + sphinx_add_hop(sp, &path[i], data); } else { struct short_channel_id scid; struct amount_msat amt; @@ -76,13 +76,17 @@ static void do_generate(int argc, char **argv, memset(&scid, i, sizeof(scid)); amt.millisatoshis = i; /* Raw: test code */ if (i == num_hops - 1) - sphinx_add_final_hop(sp, &path[i], - use_tlv, - amt, i, amt, NULL); + sphinx_add_hop(sp, &path[i], + take(onion_final_hop(NULL, + use_tlv, + amt, i, amt, + NULL))); else - sphinx_add_nonfinal_hop(sp, &path[i], - use_tlv, - &scid, amt, i); + sphinx_add_hop(sp, &path[i], + take(onion_nonfinal_hop(NULL, + use_tlv, + &scid, + amt, i))); } } @@ -183,7 +187,6 @@ static void runtest(const char *filename) struct pubkey pubkey; struct sphinx_path *path; size_t i; - enum sphinx_payload_type type; struct onionpacket *res; struct route_step *step; char *hexprivkey; @@ -207,17 +210,26 @@ static void runtest(const char *filename) /* Unpack the hops and build up the path */ hopstok = json_get_member(buffer, gentok, "hops"); json_for_each_arr(i, hop, hopstok) { + u8 *full; + size_t prepended; + payloadtok = json_get_member(buffer, hop, "payload"); typetok = json_get_member(buffer, hop, "type"); pubkeytok = json_get_member(buffer, hop, "pubkey"); payload = json_tok_bin_from_hex(ctx, buffer, payloadtok); json_to_pubkey(buffer, pubkeytok, &pubkey); if (!typetok || json_tok_streq(buffer, typetok, "legacy")) { - type = SPHINX_V0_PAYLOAD; + /* Legacy has a single 0 prepended as "realm" byte */ + full = tal_arrz(ctx, u8, 1); } else { - type = SPHINX_RAW_PAYLOAD; + /* TLV has length prepended */ + full = tal_arr(ctx, u8, 0); + towire_bigsize(&full, tal_bytelen(payload)); } - sphinx_add_raw_hop(path, &pubkey, type, payload); + prepended = tal_bytelen(full); + tal_resize(&full, prepended + tal_bytelen(payload)); + memcpy(full + prepended, payload, tal_bytelen(payload)); + sphinx_add_hop(path, &pubkey, full); } res = create_onionpacket(ctx, path, &shared_secrets); serialized = serialize_onionpacket(ctx, res); @@ -242,13 +254,20 @@ static void runtest(const char *filename) decodetok = json_get_member(buffer, toks, "decode"); json_for_each_arr(i, hop, decodetok) { + enum onion_payload_type type; + bool valid; + hexprivkey = json_strdup(ctx, buffer, hop); printf("Processing at hop %zu\n", i); step = decode_with_privkey(ctx, serialized, hexprivkey, associated_data); serialized = serialize_onionpacket(ctx, step->next); if (!serialized) errx(1, "Error serializing message."); - printf(" Type: %d\n", step->type); + onion_payload_length(step->raw_payload, + tal_bytelen(step->raw_payload), + &valid, &type); + assert(valid); + printf(" Type: %d\n", type); printf(" Payload: %s\n", tal_hex(ctx, step->raw_payload)); printf(" Next onion: %s\n", tal_hex(ctx, serialized)); printf(" Next HMAC: %s\n", tal_hexstr(ctx, step->next->mac, HMAC_SIZE)); diff --git a/doc/lightning-createonion.7 b/doc/lightning-createonion.7 index af056c9a3..4c7c1970e 100644 --- a/doc/lightning-createonion.7 +++ b/doc/lightning-createonion.7 @@ -19,17 +19,15 @@ payload destined for that node\. The following is an example of a 3 hop onion: .RS [ { - "style": "legacy", "pubkey": "022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59", - "payload": "000067000001000100000000000003e90000007b000000000000000000000000" + "payload": "00000067000001000100000000000003e90000007b000000000000000000000000000000000000000000000000" }, { - "style": "legacy", "pubkey": "035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d", - "payload": "000067000003000100000000000003e800000075000000000000000000000000" + "payload": "00000067000003000100000000000003e800000075000000000000000000000000000000000000000000000000" }, { "style": "legacy", "pubkey": "0382ce59ebf18be7d84677c2e35f23294b9992ceca95491fcf8a56c6cb2d9de199", - "payload": "000067000003000100000000000003e800000075000000000000000000000000" + "payload": "00000067000003000100000000000003e800000075000000000000000000000000000000000000000000000000" } ] .RE diff --git a/doc/lightning-createonion.7.md b/doc/lightning-createonion.7.md index e462c47db..ccf435801 100644 --- a/doc/lightning-createonion.7.md +++ b/doc/lightning-createonion.7.md @@ -19,17 +19,15 @@ payload destined for that node. The following is an example of a 3 hop onion: ```json [ { - "style": "legacy", "pubkey": "022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59", - "payload": "000067000001000100000000000003e90000007b000000000000000000000000" + "payload": "00000067000001000100000000000003e90000007b000000000000000000000000000000000000000000000000" }, { - "style": "legacy", "pubkey": "035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d", - "payload": "000067000003000100000000000003e800000075000000000000000000000000" + "payload": "00000067000003000100000000000003e800000075000000000000000000000000000000000000000000000000" }, { "style": "legacy", "pubkey": "0382ce59ebf18be7d84677c2e35f23294b9992ceca95491fcf8a56c6cb2d9de199", - "payload": "000067000003000100000000000003e800000075000000000000000000000000" + "payload": "00000067000003000100000000000003e800000075000000000000000000000000000000000000000000000000" } ] ``` diff --git a/lightningd/Makefile b/lightningd/Makefile index 47fbf8fc4..11686eae9 100644 --- a/lightningd/Makefile +++ b/lightningd/Makefile @@ -45,6 +45,7 @@ LIGHTNINGD_COMMON_OBJS := \ common/memleak.o \ common/msg_queue.o \ common/node_id.o \ + common/onion.o \ common/param.o \ common/per_peer_state.o \ common/permute_tx.o \ diff --git a/lightningd/pay.c b/lightningd/pay.c index cd22cbc1c..b8a8675b0 100644 --- a/lightningd/pay.c +++ b/lightningd/pay.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -725,6 +726,7 @@ send_payment(struct lightningd *ld, struct sphinx_path *path; struct pubkey pubkey; bool final_tlv, ret; + u8 *onion; /* Expiry for HTLCs is absolute. And add one to give some margin. */ base_expiry = get_block_height(ld->topology) + 1; @@ -739,11 +741,12 @@ send_payment(struct lightningd *ld, ret = pubkey_from_node_id(&pubkey, &ids[i]); assert(ret); - sphinx_add_nonfinal_hop(path, &pubkey, + sphinx_add_hop(path, &pubkey, + take(onion_nonfinal_hop(NULL, should_use_tlv(route[i].style), &route[i + 1].channel_id, route[i + 1].amount, - base_expiry + route[i + 1].delay); + base_expiry + route[i + 1].delay))); } /* And finally set the final hop to the special values in @@ -763,15 +766,17 @@ send_payment(struct lightningd *ld, if (!final_tlv && payment_secret) final_tlv = true; - if (!sphinx_add_final_hop(path, &pubkey, - final_tlv, - route[i].amount, - base_expiry + route[i].delay, - route[i].amount, payment_secret)) { + onion = onion_final_hop(cmd, + final_tlv, + route[i].amount, + base_expiry + route[i].delay, + route[i].amount, payment_secret); + if (!onion) { return command_fail(cmd, PAY_DESTINATION_PERM_FAIL, "Destination does not support" " payment_secret"); } + sphinx_add_hop(path, &pubkey, onion); /* Now, do we already have a payment? */ payment = wallet_payment_by_hash(tmpctx, ld->wallet, rhash); @@ -1381,8 +1386,7 @@ static struct command_result *json_createonion(struct command *cmd, sp = sphinx_path_new_with_key(cmd, assocdata, session_key); for (size_t i=0; i #include #include +#include #include #include #include @@ -655,6 +656,8 @@ static void channel_resolve_reply(struct subd *gossip, const u8 *msg, */ struct htlc_accepted_hook_payload { struct route_step *route_step; + /* NULL if it couldn't be parsed! */ + struct onion_payload *payload; struct htlc_in *hin; struct channel *channel; struct lightningd *ld; @@ -746,34 +749,43 @@ static void htlc_accepted_hook_serialize(struct htlc_accepted_hook_payload *p, s32 expiry = hin->cltv_expiry, blockheight = p->ld->topology->tip->height; json_object_start(s, "onion"); - json_add_hex_talarr (s, "payload", rs->raw_payload); - if (rs->type == SPHINX_V0_PAYLOAD) { - if (deprecated_apis) { - json_object_start(s, "per_hop_v0"); - json_add_string(s, "realm", "00"); - json_add_short_channel_id(s, "short_channel_id", &rs->payload.v0.channel_id); - json_add_amount_msat_only(s, "forward_amount", rs->payload.v0.amt_forward); - json_add_u64(s, "outgoing_cltv_value", rs->payload.v0.outgoing_cltv); - json_object_end(s); + json_add_hex_talarr(s, "payload", rs->raw_payload); + if (p->payload) { + switch (p->payload->type) { + case ONION_V0_PAYLOAD: + if (deprecated_apis) { + json_object_start(s, "per_hop_v0"); + json_add_string(s, "realm", "00"); + json_add_short_channel_id(s, "short_channel_id", + p->payload->forward_channel); + json_add_amount_msat_only(s, "forward_amount", + p->payload->amt_to_forward); + json_add_u64(s, "outgoing_cltv_value", + p->payload->outgoing_cltv); + json_object_end(s); + } + json_add_string(s, "type", "legacy"); + break; + + case ONION_TLV_PAYLOAD: + json_add_string(s, "type", "tlv"); + break; } - json_add_string(s, "type", "legacy"); - } else if (rs->type == SPHINX_TLV_PAYLOAD) { - json_add_string(s, "type", "tlv"); - } - if (rs->forward_channel) - json_add_short_channel_id(s, "short_channel_id", - rs->forward_channel); - if (rs->amt_to_forward) + if (p->payload->forward_channel) + json_add_short_channel_id(s, "short_channel_id", + p->payload->forward_channel); json_add_amount_msat_only(s, "forward_amount", - *rs->amt_to_forward); - if (rs->outgoing_cltv) - json_add_u32(s, "outgoing_cltv_value", *rs->outgoing_cltv); - /* These are specified together in TLV, so only print total_msat if - * payment_secret set (ie. modern, and final hop) */ - if (rs->payment_secret) { - json_add_amount_msat_only(s, "total_msat", *rs->total_msat); - json_add_secret(s, "payment_secret", rs->payment_secret); + p->payload->amt_to_forward); + json_add_u32(s, "outgoing_cltv_value", p->payload->outgoing_cltv); + /* These are specified together in TLV, so only print total_msat + * if payment_secret set (ie. modern, and final hop) */ + if (p->payload->payment_secret) { + json_add_amount_msat_only(s, "total_msat", + *p->payload->total_msat); + json_add_secret(s, "payment_secret", + p->payload->payment_secret); + } } json_add_hex_talarr(s, "next_onion", p->next_onion); json_add_secret(s, "shared_secret", hin->shared_secret); @@ -787,45 +799,6 @@ static void htlc_accepted_hook_serialize(struct htlc_accepted_hook_payload *p, json_object_end(s); } -/* Make sure that we can continue with a default action if the htlc_accepted - * hook tells us to. This means enforcing that we have the necessary - * information to forward, fail or accept, and that the TVL payload is encoded - * correctly. */ -static bool htlc_accepted_can_continue(struct route_step *rs) -{ - if (rs->type == SPHINX_TLV_PAYLOAD && - !tlv_payload_is_valid(rs->payload.tlv, NULL)) { - SUPERVERBOSE("Encoding of TLV payload is invalid"); - return false; - } - - /* 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. - * - MUST NOT include `short_channel_id` for the final node. - * - * The reader: - * - MUST return an error if `amt_to_forward` or `outgoing_cltv_value` are not present. - */ - if (rs->amt_to_forward == NULL) { - SUPERVERBOSE("Missing amt_to_forward in payload"); - return false; - } - - if (rs->outgoing_cltv == NULL) { - SUPERVERBOSE("Missing outgoing_cltv_value in payload"); - return false; - } - - if (rs->nextcase && rs->forward_channel == NULL) { - SUPERVERBOSE("Missing short_channel_id in payload"); - return false; - } - return true; -} - /** * Callback when a plugin answers to the htlc_accepted hook */ @@ -846,17 +819,18 @@ htlc_accepted_hook_callback(struct htlc_accepted_hook_payload *request, switch (result) { case htlc_accepted_continue: - if (!htlc_accepted_can_continue(rs)) { + /* *Now* we barf if it failed to decode */ + if (!request->payload) { log_debug(channel->log, "Failing HTLC because of an invalid payload"); failure_code = WIRE_INVALID_ONION_PAYLOAD; fail_in_htlc(hin, failure_code, NULL, NULL); - }else if (rs->nextcase == ONION_FORWARD) { + } else if (rs->nextcase == ONION_FORWARD) { struct gossip_resolve *gr = tal(ld, struct gossip_resolve); gr->next_onion = serialize_onionpacket(gr, rs->next); - gr->next_channel = *rs->forward_channel; - gr->amt_to_forward = *rs->amt_to_forward; - gr->outgoing_cltv_value = *rs->outgoing_cltv; + gr->next_channel = *request->payload->forward_channel; + gr->amt_to_forward = request->payload->amt_to_forward; + gr->outgoing_cltv_value = request->payload->outgoing_cltv; gr->hin = hin; req = towire_gossip_get_channel_peer(tmpctx, &gr->next_channel); @@ -867,21 +841,30 @@ htlc_accepted_hook_callback(struct htlc_accepted_hook_payload *request, channel_resolve_reply, gr); } else handle_localpay(hin, hin->cltv_expiry, &hin->payment_hash, - *rs->amt_to_forward, - *rs->outgoing_cltv, - *rs->total_msat, - rs->payment_secret); + request->payload->amt_to_forward, + request->payload->outgoing_cltv, + *request->payload->total_msat, + request->payload->payment_secret); break; case htlc_accepted_fail: log_debug(channel->log, "Failing incoming HTLC as instructed by plugin hook"); - if ((failure_code & UPDATE) && rs->nextcase == ONION_END) { - log_broken(channel->log, - "htlc_acccepted hook: Can't return failure %u on last hop!", - failure_code); - failure_code = WIRE_TEMPORARY_NODE_FAILURE; + if (failure_code & UPDATE) { + if (rs->nextcase == ONION_END) { + log_broken(channel->log, + "htlc_acccepted hook: Can't return failure %u on last hop!", + failure_code); + failure_code = WIRE_TEMPORARY_NODE_FAILURE; + } else if (!request->payload) { + log_broken(channel->log, + "htlc_acccepted hook: Can't return failure %u on undecodable onion!", + failure_code); + failure_code = WIRE_TEMPORARY_NODE_FAILURE; + } } - fail_in_htlc(hin, failure_code, NULL, rs->forward_channel); + fail_in_htlc(hin, failure_code, NULL, + request->payload + ? request->payload->forward_channel : NULL); break; case htlc_accepted_resolve: fulfill_htlc(hin, &payment_preimage); @@ -989,6 +972,7 @@ static bool peer_accepted_htlc(struct channel *channel, u64 id, hook_payload = tal(hin, struct htlc_accepted_hook_payload); hook_payload->route_step = tal_steal(hook_payload, rs); + hook_payload->payload = onion_decode(hook_payload, rs); hook_payload->ld = ld; hook_payload->hin = hin; hook_payload->channel = channel; diff --git a/tests/test_pay.py b/tests/test_pay.py index 0c997ac9b..eafa6102e 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -2449,22 +2449,25 @@ def test_createonion_rpc(node_factory): l1 = node_factory.get_node() hops = [{ - "style": "legacy", "pubkey": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619", - "payload": "0000000000000000000000000000000000000000" + # legacy + "payload": "000000000000000000000000000000000000000000000000000000000000000000" }, { "pubkey": "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c", - "payload": "0101010101010101000000000000000100000001" + # tlv (20 bytes) + "payload": "140101010101010101000000000000000100000001" }, { "pubkey": "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007", - "payload": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff" + # TLV (256 bytes) + "payload": "fd0100000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff" }, { "pubkey": "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991", - "payload": "0303030303030303000000000000000300000003" + # tlv (20 bytes) + "payload": "140303030303030303000000000000000300000003" }, { - "style": "legacy", "pubkey": "02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145", - "payload": "0404040404040404000000000000000400000004" + # legacy + "payload": "000404040404040404000000000000000400000004000000000000000000000000" }] res = l1.rpc.createonion(hops=hops, assocdata="BB" * 32) @@ -2475,6 +2478,7 @@ def test_createonion_rpc(node_factory): session_key="41" * 32) # The trailer is generated using the filler and can be ued as a # checksum. This trailer is from the test-vector in the specs. + print(res) assert(res['onion'].endswith('be89e4701eb870f8ed64fafa446c78df3ea')) @@ -2491,7 +2495,8 @@ def test_sendonion_rpc(node_factory): def serialize_payload(n): block, tx, out = n['channel'].split('x') payload = hexlify(struct.pack( - "!QQL", + "!BQQL", + 0, int(block) << 40 | int(tx) << 16 | int(out), int(n['amount_msat']), blockheight + n['delay'])).decode('ASCII') @@ -2503,14 +2508,12 @@ def test_sendonion_rpc(node_factory): for h, n in zip(route[:-1], route[1:]): # We tell the node h about the parameters to use for n (a.k.a. h + 1) hops.append({ - "style": "legacy", "pubkey": h['id'], "payload": serialize_payload(n) }) # The last hop has a special payload: hops.append({ - "style": "legacy", "pubkey": route[-1]['id'], "payload": serialize_payload(route[-1]) }) diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c index 8dad28495..228b8ab8b 100644 --- a/wallet/test/run-wallet.c +++ b/wallet/test/run-wallet.c @@ -395,6 +395,10 @@ enum watch_result onchaind_funding_spent(struct channel *channel UNNEEDED, const struct bitcoin_tx *tx UNNEEDED, u32 blockheight UNNEEDED) { fprintf(stderr, "onchaind_funding_spent called!\n"); abort(); } +/* Generated stub for onion_decode */ +struct onion_payload *onion_decode(const tal_t *ctx UNNEEDED, + const struct route_step *rs UNNEEDED) +{ fprintf(stderr, "onion_decode called!\n"); abort(); } /* Generated stub for onion_type_name */ const char *onion_type_name(int e UNNEEDED) { fprintf(stderr, "onion_type_name called!\n"); abort(); } @@ -545,10 +549,6 @@ void subd_req_(const tal_t *ctx UNNEEDED, /* Generated stub for subd_send_msg */ void subd_send_msg(struct subd *sd UNNEEDED, const u8 *msg_out UNNEEDED) { fprintf(stderr, "subd_send_msg called!\n"); abort(); } -/* Generated stub for tlv_payload_is_valid */ -bool tlv_payload_is_valid(const struct tlv_tlv_payload *record UNNEEDED, - size_t *err_index UNNEEDED) -{ fprintf(stderr, "tlv_payload_is_valid called!\n"); abort(); } /* Generated stub for topology_add_sync_waiter_ */ void topology_add_sync_waiter_(const tal_t *ctx UNNEEDED, struct chain_topology *topo UNNEEDED,