diff --git a/common/onion.c b/common/onion.c index 18ba5e806..5fdcf7579 100644 --- a/common/onion.c +++ b/common/onion.c @@ -109,10 +109,11 @@ u8 *onion_final_hop(const tal_t *ctx, return make_tlv_hop(ctx, tlv); } -/* Returns true if valid, and fills in len. */ +/* 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) { /* *len will incorporate bytes we read from cursor */ @@ -127,6 +128,19 @@ static bool pull_payload_length(const u8 **cursor, 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 (has_realm && *len == 0) { + if (type) + *type = ONION_V0_PAYLOAD; + assert(*cursor - start == 1); + *len = 1 + 32; + return true; + } + /* 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 @@ -142,6 +156,8 @@ static bool pull_payload_length(const u8 **cursor, return false; } + if (type) + *type = ONION_TLV_PAYLOAD; *len += (*cursor - start); return true; } @@ -150,10 +166,11 @@ static bool pull_payload_length(const u8 **cursor, } size_t onion_payload_length(const u8 *raw_payload, size_t len, bool has_realm, - bool *valid) + bool *valid, + enum onion_payload_type *type) { size_t max = len, payload_len; - *valid = pull_payload_length(&raw_payload, &max, has_realm, &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) @@ -210,12 +227,31 @@ 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, true, &len)) { + if (!pull_payload_length(&cursor, &max, true, &p->type, &len)) { *failtlvtype = 0; *failtlvpos = tal_bytelen(rs->raw_payload); goto fail_no_tlv; } + /* Very limited legacy handling: forward only. */ + if (p->type == ONION_V0_PAYLOAD && rs->nextcase == ONION_FORWARD) { + p->forward_channel = tal(p, struct short_channel_id); + fromwire_short_channel_id(&cursor, &max, p->forward_channel); + p->total_msat = NULL; + p->amt_to_forward = fromwire_amount_msat(&cursor, &max); + p->outgoing_cltv = fromwire_u32(&cursor, &max); + p->payment_secret = NULL; + p->blinding = NULL; + /* We can't handle blinding with a legacy payload */ + if (blinding) + return tal_free(p); + /* If they somehow got an invalid onion this far, fail. */ + if (!cursor) + return tal_free(p); + p->tlv = NULL; + return p; + } + /* We do this manually so we can accept extra types, and get * error off and type. */ tlv = tlv_tlv_payload_new(p); diff --git a/common/onion.h b/common/onion.h index 46ccacb27..6f3045faf 100644 --- a/common/onion.h +++ b/common/onion.h @@ -6,7 +6,14 @@ 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; @@ -43,6 +50,7 @@ u8 *onion_final_hop(const tal_t *ctx, * @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. * * If @valid is set, there is room for the HMAC immediately following, * as the return value is <= ROUTING_INFO_SIZE - HMAC_SIZE. Otherwise, @@ -50,7 +58,8 @@ u8 *onion_final_hop(const tal_t *ctx, */ size_t onion_payload_length(const u8 *raw_payload, size_t len, bool has_realm, - bool *valid); + bool *valid, + enum onion_payload_type *type); /** * onion_decode: decode payload from a decrypted onion. diff --git a/common/sphinx.c b/common/sphinx.c index 9c0064db4..1eb4496ea 100644 --- a/common/sphinx.c +++ b/common/sphinx.c @@ -627,7 +627,7 @@ struct route_step *process_onionpacket( payload_size = onion_payload_length(paddedheader, tal_bytelen(msg->routinginfo), has_realm, - &valid); + &valid, NULL); /* Can't decode? Treat it as terminal. */ if (!valid) { diff --git a/common/test/run-gossmap_local.c b/common/test/run-gossmap_local.c index 85e522448..6f1b4b724 100644 --- a/common/test/run-gossmap_local.c +++ b/common/test/run-gossmap_local.c @@ -25,10 +25,6 @@ void towire_bigsize(u8 **pptr UNNEEDED, const bigsize_t val UNNEEDED) /* Generated stub for towire_channel_id */ void towire_channel_id(u8 **pptr UNNEEDED, const struct channel_id *channel_id UNNEEDED) { fprintf(stderr, "towire_channel_id called!\n"); abort(); } -/* Generated stub for type_to_string_ */ -const char *type_to_string_(const tal_t *ctx UNNEEDED, const char *typename UNNEEDED, - union printable_types u UNNEEDED) -{ fprintf(stderr, "type_to_string_ called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ /* Canned gossmap, taken from tests/test_gossip.py's diff --git a/common/test/run-route-specific.c b/common/test/run-route-specific.c index c3b375aea..e5aa986cb 100644 --- a/common/test/run-route-specific.c +++ b/common/test/run-route-specific.c @@ -51,10 +51,6 @@ void towire_tlv(u8 **pptr UNNEEDED, /* Generated stub for towire_wireaddr */ void towire_wireaddr(u8 **pptr UNNEEDED, const struct wireaddr *addr UNNEEDED) { fprintf(stderr, "towire_wireaddr called!\n"); abort(); } -/* Generated stub for type_to_string_ */ -const char *type_to_string_(const tal_t *ctx UNNEEDED, const char *typename UNNEEDED, - union printable_types u UNNEEDED) -{ fprintf(stderr, "type_to_string_ called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ static void write_to_store(int store_fd, const u8 *msg) diff --git a/common/test/run-route.c b/common/test/run-route.c index 5c575c6fd..74aaa1eee 100644 --- a/common/test/run-route.c +++ b/common/test/run-route.c @@ -44,10 +44,6 @@ void towire_tlv(u8 **pptr UNNEEDED, /* Generated stub for towire_wireaddr */ void towire_wireaddr(u8 **pptr UNNEEDED, const struct wireaddr *addr UNNEEDED) { fprintf(stderr, "towire_wireaddr called!\n"); abort(); } -/* Generated stub for type_to_string_ */ -const char *type_to_string_(const tal_t *ctx UNNEEDED, const char *typename UNNEEDED, - union printable_types u UNNEEDED) -{ fprintf(stderr, "type_to_string_ called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ static void write_to_store(int store_fd, const u8 *msg) diff --git a/common/test/run-sphinx-xor_cipher_stream.c b/common/test/run-sphinx-xor_cipher_stream.c index 26781985b..dc1bd585c 100644 --- a/common/test/run-sphinx-xor_cipher_stream.c +++ b/common/test/run-sphinx-xor_cipher_stream.c @@ -91,7 +91,8 @@ struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents /* Generated stub for onion_payload_length */ size_t onion_payload_length(const u8 *raw_payload UNNEEDED, size_t len UNNEEDED, bool has_realm UNNEEDED, - bool *valid UNNEEDED) + bool *valid UNNEEDED, + enum onion_payload_type *type UNNEEDED) { fprintf(stderr, "onion_payload_length called!\n"); abort(); } /* Generated stub for pubkey_from_node_id */ bool pubkey_from_node_id(struct pubkey *key UNNEEDED, const struct node_id *id UNNEEDED) diff --git a/devtools/onion.c b/devtools/onion.c index 7b81a4f9a..f750bedd4 100644 --- a/devtools/onion.c +++ b/devtools/onion.c @@ -280,7 +280,7 @@ static void runtest(const char *filename) errx(1, "Error serializing message."); onion_payload_length(step->raw_payload, tal_bytelen(step->raw_payload), - true, &valid); + true, &valid, NULL); assert(valid); printf(" Payload: %s\n", tal_hex(ctx, step->raw_payload)); printf(" Next onion: %s\n", tal_hex(ctx, serialized)); diff --git a/lightningd/peer_htlcs.c b/lightningd/peer_htlcs.c index ea859ebeb..278b80527 100644 --- a/lightningd/peer_htlcs.c +++ b/lightningd/peer_htlcs.c @@ -993,7 +993,15 @@ static void htlc_accepted_hook_serialize(struct htlc_accepted_hook_payload *p, json_add_hex_talarr(s, "payload", rs->raw_payload); if (p->payload) { - json_add_string(s, "type", "tlv"); + switch (p->payload->type) { + case ONION_V0_PAYLOAD: + json_add_string(s, "type", "legacy"); + break; + + case ONION_TLV_PAYLOAD: + json_add_string(s, "type", "tlv"); + break; + } if (p->payload->forward_channel) json_add_short_channel_id(s, "short_channel_id", diff --git a/tests/test_pay.py b/tests/test_pay.py index 0ef99152a..29235c571 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -5169,7 +5169,6 @@ def test_sendpay_grouping(node_factory, bitcoind): assert([p['status'] for p in pays] == ['failed', 'failed', 'complete']) -@pytest.mark.xfail("needs lecacy onion support") def test_legacyonion(node_factory, bitcoind): # We have to replicate the topology we created onion with, exactly. l1, l2, l3 = node_factory.line_graph(3, fundchannel=False)