diff --git a/channeld/channeld.c b/channeld/channeld.c index bef107c66f4e..b5987249b112 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -1614,6 +1614,10 @@ static void marshall_htlc_info(const tal_t *ctx, assert(!htlc->failed); f.id = htlc->id; f.payment_preimage = *htlc->r; + if (htlc->attr_data) + f.attr_data = tal_dup(fulfilled, struct attribution_data, htlc->attr_data); + else + f.attr_data = NULL; tal_arr_expand(fulfilled, f); } else { assert(!htlc->r); @@ -2619,9 +2623,10 @@ static void handle_peer_fulfill_htlc(struct peer *peer, const u8 *msg) struct preimage preimage; enum channel_remove_err e; struct htlc *h; + struct tlv_update_fulfill_htlc_tlvs *tlvs_attr_data; - if (!fromwire_update_fulfill_htlc(msg, &channel_id, - &id, &preimage)) { + if (!fromwire_update_fulfill_htlc(tmpctx, msg, &channel_id, + &id, &preimage, &tlvs_attr_data)) { peer_failed_warn(peer->pps, &peer->channel_id, "Bad update_fulfill_htlc %s", tal_hex(msg, msg)); } @@ -2629,7 +2634,16 @@ static void handle_peer_fulfill_htlc(struct peer *peer, const u8 *msg) e = channel_fulfill_htlc(peer->channel, LOCAL, id, &preimage, &h); switch (e) { case CHANNEL_ERR_REMOVE_OK: - /* FIXME: We could send preimages to master immediately. */ + if (tlvs_attr_data && tlvs_attr_data->attribution_data) { + h->attr_data = tal(h, struct attribution_data); + h->attr_data->htlc_hold_time = tal_dup_arr(h->attr_data, u8, + tlvs_attr_data->attribution_data->htlc_hold_times, 80, 0); + h->attr_data->truncated_hmac = tal_dup_arr(h->attr_data, u8, + tlvs_attr_data->attribution_data->truncated_hmacs, 840, 0); + } else { + h->attr_data = NULL; + } + start_commit_timer(peer); return; /* These shouldn't happen, because any offered HTLC (which would give @@ -2655,10 +2669,11 @@ static void handle_peer_fail_htlc(struct peer *peer, const u8 *msg) u8 *reason; struct htlc *htlc; struct failed_htlc *f; + struct tlv_update_fail_htlc_tlvs *tlvs_attr_data; /* reason is not an onionreply because spec doesn't know about that */ if (!fromwire_update_fail_htlc(msg, msg, - &channel_id, &id, &reason)) { + &channel_id, &id, &reason, &tlvs_attr_data)) { peer_failed_warn(peer->pps, &peer->channel_id, "Bad update_fail_htlc %s", tal_hex(msg, msg)); } @@ -2669,7 +2684,12 @@ static void handle_peer_fail_htlc(struct peer *peer, const u8 *msg) htlc->failed = f = tal(htlc, struct failed_htlc); f->id = id; f->sha256_of_onion = NULL; - f->onion = new_onionreply(f, take(reason)); + struct attribution_data *attr = tal(f, struct attribution_data); + attr->htlc_hold_time = tal_dup_arr(f, u8, tlvs_attr_data->attribution_data->htlc_hold_times, 80, 0); + attr->truncated_hmac = tal_dup_arr(f, u8, tlvs_attr_data->attribution_data->truncated_hmacs, 840, 0); + f->onion = new_onionreply(f, take(reason), + attr); + start_commit_timer(peer); return; } @@ -5137,12 +5157,22 @@ static void send_fail_or_fulfill(struct peer *peer, const struct htlc *h) f->sha256_of_onion, f->badonion); } else { + struct tlv_update_fail_htlc_tlvs *attr_data_tlv = tlv_update_fail_htlc_tlvs_new(peer); + attr_data_tlv->attribution_data = tal(peer, struct tlv_update_fail_htlc_tlvs_attribution_data); + memcpy(attr_data_tlv->attribution_data->htlc_hold_times, f->onion->attr_data->htlc_hold_time, 80); + memcpy(attr_data_tlv->attribution_data->truncated_hmacs, f->onion->attr_data->truncated_hmac, 840); msg = towire_update_fail_htlc(peer, &peer->channel_id, h->id, - f->onion->contents); + f->onion->contents, attr_data_tlv); } } else if (h->r) { + struct tlv_update_fulfill_htlc_tlvs *attr_data_tlv = tlv_update_fulfill_htlc_tlvs_new(peer); + attr_data_tlv->attribution_data = tal(peer, struct tlv_update_fulfill_htlc_tlvs_attribution_data); + if (h->attr_data != NULL) { + memcpy(attr_data_tlv->attribution_data->htlc_hold_times, h->attr_data->htlc_hold_time, 80); + memcpy(attr_data_tlv->attribution_data->truncated_hmacs, h->attr_data->truncated_hmac, 840); + } msg = towire_update_fulfill_htlc(NULL, &peer->channel_id, h->id, - h->r); + h->r, attr_data_tlv); } else peer_failed_warn(peer->pps, &peer->channel_id, "HTLC %"PRIu64" state %s not failed/fulfilled", diff --git a/channeld/channeld_htlc.h b/channeld/channeld_htlc.h index ed2efe202a48..5640c5d75168 100644 --- a/channeld/channeld_htlc.h +++ b/channeld/channeld_htlc.h @@ -34,6 +34,9 @@ struct htlc { /* Should we immediately fail this htlc? */ bool fail_immediate; + + /* Attribution Data */ + struct attribution_data *attr_data; }; static inline bool htlc_has(const struct htlc *h, int flag) diff --git a/common/htlc_wire.c b/common/htlc_wire.c index 5ccd75795ad5..6796aba84d79 100644 --- a/common/htlc_wire.c +++ b/common/htlc_wire.c @@ -148,6 +148,11 @@ void towire_fulfilled_htlc(u8 **pptr, const struct fulfilled_htlc *fulfilled) { towire_u64(pptr, fulfilled->id); towire_preimage(pptr, &fulfilled->payment_preimage); + towire_bool(pptr, fulfilled->attr_data != NULL); + if (fulfilled->attr_data) { + towire_u8_array(pptr, fulfilled->attr_data->htlc_hold_time, 80); + towire_u8_array(pptr, fulfilled->attr_data->truncated_hmac, 840); + } } void towire_failed_htlc(u8 **pptr, const struct failed_htlc *failed) @@ -272,11 +277,20 @@ struct existing_htlc *fromwire_existing_htlc(const tal_t *ctx, return existing; } -void fromwire_fulfilled_htlc(const u8 **cursor, size_t *max, - struct fulfilled_htlc *fulfilled) +struct fulfilled_htlc *fromwire_fulfilled_htlc(const tal_t *ctx, const u8 **cursor, size_t *max) { + struct fulfilled_htlc *fulfilled = tal(ctx, struct fulfilled_htlc); fulfilled->id = fromwire_u64(cursor, max); fromwire_preimage(cursor, max, &fulfilled->payment_preimage); + bool has_attr = fromwire_bool(cursor, max); + if (has_attr) { + fulfilled->attr_data = tal(fulfilled, struct attribution_data); + fulfilled->attr_data->htlc_hold_time = fromwire_tal_arrn(fulfilled->attr_data, cursor, max, 80); + fulfilled->attr_data->truncated_hmac = fromwire_tal_arrn(fulfilled->attr_data, cursor, max, 840); + } else { + fulfilled->attr_data = NULL; + } + return fulfilled; } struct failed_htlc *fromwire_failed_htlc(const tal_t *ctx, const u8 **cursor, size_t *max) diff --git a/common/htlc_wire.h b/common/htlc_wire.h index 2ef90e9e87d5..85d1bbf5a01e 100644 --- a/common/htlc_wire.h +++ b/common/htlc_wire.h @@ -40,6 +40,7 @@ struct existing_htlc { struct fulfilled_htlc { u64 id; struct preimage payment_preimage; + struct attribution_data *attr_data; }; struct failed_htlc { @@ -90,8 +91,8 @@ struct added_htlc *fromwire_added_htlc(const tal_t *ctx, const u8 **cursor, size_t *max); struct existing_htlc *fromwire_existing_htlc(const tal_t *ctx, const u8 **cursor, size_t *max); -void fromwire_fulfilled_htlc(const u8 **cursor, size_t *max, - struct fulfilled_htlc *fulfilled); +struct fulfilled_htlc *fromwire_fulfilled_htlc(const tal_t *ctx, const u8 **cursor, + size_t *max); struct failed_htlc *fromwire_failed_htlc(const tal_t *ctx, const u8 **cursor, size_t *max); void fromwire_changed_htlc(const u8 **cursor, size_t *max, diff --git a/common/onionreply.c b/common/onionreply.c index 74a20a57ce28..85e5769be0b9 100644 --- a/common/onionreply.c +++ b/common/onionreply.c @@ -7,6 +7,10 @@ void towire_onionreply(u8 **cursor, const struct onionreply *r) { towire_u16(cursor, tal_count(r->contents)); towire_u8_array(cursor, r->contents, tal_count(r->contents)); + if (r->attr_data) { + towire_u8_array(cursor, r->attr_data->htlc_hold_time, 80); + towire_u8_array(cursor, r->attr_data->truncated_hmac, 840); + } } struct onionreply *fromwire_onionreply(const tal_t *ctx, @@ -15,6 +19,14 @@ struct onionreply *fromwire_onionreply(const tal_t *ctx, struct onionreply *r = tal(ctx, struct onionreply); r->contents = fromwire_tal_arrn(r, cursor, max, fromwire_u16(cursor, max)); + if (*max >= 80 + 840) { + r->attr_data = tal(ctx, struct attribution_data); + r->attr_data->htlc_hold_time = fromwire_tal_arrn(r, cursor, max, 80); + r->attr_data->truncated_hmac = fromwire_tal_arrn(r, cursor, max, 840); + } else { + r->attr_data = NULL; + } + if (!*cursor) return tal_free(r); return r; @@ -30,12 +42,26 @@ struct onionreply *dup_onionreply(const tal_t *ctx, n = tal(ctx, struct onionreply); n->contents = tal_dup_talarr(n, u8, r->contents); + if (r->attr_data) { + n->attr_data = tal(ctx, struct attribution_data); + n->attr_data->htlc_hold_time = tal_dup_talarr(r, u8, r->attr_data->htlc_hold_time); + n->attr_data->truncated_hmac = tal_dup_talarr(r, u8, r->attr_data->truncated_hmac); + } else { + n->attr_data = NULL; + } return n; } -struct onionreply *new_onionreply(const tal_t *ctx, const u8 *contents TAKES) +struct onionreply *new_onionreply(const tal_t *ctx, const u8 *contents TAKES, const struct attribution_data *attr_data TAKES) { struct onionreply *r = tal(ctx, struct onionreply); r->contents = tal_dup_talarr(r, u8, contents); + if (attr_data) { + r->attr_data = tal(ctx, struct attribution_data); + r->attr_data->htlc_hold_time = tal_dup_talarr(r, u8, attr_data->htlc_hold_time); + r->attr_data->truncated_hmac = tal_dup_talarr(r, u8, attr_data->truncated_hmac); + } else { + r->attr_data = NULL; + } return r; } diff --git a/common/onionreply.h b/common/onionreply.h index 759a4df18ecd..848994139a51 100644 --- a/common/onionreply.h +++ b/common/onionreply.h @@ -4,9 +4,16 @@ #include #include +/* A separate type for attribution data. */ +struct attribution_data { + u8 *htlc_hold_time; + u8 *truncated_hmac; +}; + /* A separate type for an onion reply, to differentiate from a wire msg. */ struct onionreply { u8 *contents; + struct attribution_data *attr_data; }; /** @@ -20,5 +27,5 @@ struct onionreply *fromwire_onionreply(const tal_t *ctx, struct onionreply *dup_onionreply(const tal_t *ctx, const struct onionreply *r TAKES); -struct onionreply *new_onionreply(const tal_t *ctx, const u8 *contents TAKES); +struct onionreply *new_onionreply(const tal_t *ctx, const u8 *contents TAKES, const struct attribution_data *attr_data TAKES); #endif /* LIGHTNING_COMMON_ONIONREPLY_H */ diff --git a/common/sphinx.c b/common/sphinx.c index 5cecf68777b5..ab2fbdd18fb7 100644 --- a/common/sphinx.c +++ b/common/sphinx.c @@ -776,6 +776,7 @@ struct onionreply *create_onionreply(const tal_t *ctx, towire(&reply->contents, payload, tal_count(payload)); tal_free(payload); + reply->attr_data = NULL; return reply; } @@ -783,7 +784,7 @@ struct onionreply *wrap_onionreply(const tal_t *ctx, const struct secret *shared_secret, const struct onionreply *reply) { - struct secret key; + struct secret key, attr_key; struct onionreply *result = tal(ctx, struct onionreply); /* BOLT #4: @@ -797,9 +798,109 @@ struct onionreply *wrap_onionreply(const tal_t *ctx, subkey_from_hmac("ammag", shared_secret, &key); result->contents = tal_dup_talarr(result, u8, reply->contents); xor_cipher_stream(result->contents, &key, tal_bytelen(result->contents)); + + if (reply->attr_data) { + result->attr_data = tal(ctx, struct attribution_data); + subkey_from_hmac("ammagext", shared_secret, &attr_key); + result->attr_data->htlc_hold_time = tal_dup_talarr(result, u8, reply->attr_data->htlc_hold_time); + xor_cipher_stream_off(&attr_key, 0, result->attr_data->htlc_hold_time, tal_bytelen(result->attr_data->htlc_hold_time)); + result->attr_data->truncated_hmac = tal_dup_talarr(result, u8, reply->attr_data->truncated_hmac); + xor_cipher_stream_off(&attr_key, + tal_bytelen(result->attr_data->htlc_hold_time), + result->attr_data->truncated_hmac, + tal_bytelen(result->attr_data->truncated_hmac)); + } else { + result->attr_data = NULL; + } + return result; } +static void write_downstream_hmacs(struct crypto_auth_hmacsha256_state *state, int pos, u8 *truncated_hmac) { + int hmac_idx = MAX_HOPS + MAX_HOPS - pos - 1; + for (int i = 0; i < pos; i++) { + hmac_update(state, truncated_hmac + (hmac_idx * TRUNC_HMAC_LEN), TRUNC_HMAC_LEN); + int block_size = MAX_HOPS - i - 1; + hmac_idx += block_size; + } +} + +static void add_hmacs_to_attribution_data(struct onionreply *failonion, struct secret *shared_secret) { + struct secret um_key; + subkey_from_hmac("um", shared_secret, &um_key); + for (int i = 0; i < MAX_HOPS; i++) { + struct crypto_auth_hmacsha256_state state; + struct hmac hmac; + int pos = MAX_HOPS - i - 1; + hmac_start(&state, um_key.data, sizeof(um_key.data)); + hmac_update(&state, failonion->contents, tal_bytelen(failonion->contents)); + hmac_update(&state, failonion->attr_data->htlc_hold_time, (pos + 1) * HOLD_TIME_LEN); + write_downstream_hmacs(&state, pos, failonion->attr_data->truncated_hmac); + hmac_done(&state, &hmac); + memcpy(failonion->attr_data->truncated_hmac + (i * TRUNC_HMAC_LEN), hmac.bytes, TRUNC_HMAC_LEN); + } + +} + +void update_attributable_data(struct onionreply *failonion, u32 hold_times, struct secret *shared_secret) { + if (!failonion->attr_data) { + failonion->attr_data = tal(failonion, struct attribution_data); + failonion->attr_data->htlc_hold_time = tal_arrz(failonion, u8, 80); + failonion->attr_data->truncated_hmac = tal_arrz(failonion, u8, 840); + } else { + /* Right shift */ + memmove(&failonion->attr_data->htlc_hold_time[HOLD_TIME_LEN], failonion->attr_data->htlc_hold_time, HOLD_TIME_LEN * (MAX_HOPS - 1)); + int src_index = HMAC_COUNT - 2; + int dest_index = HMAC_COUNT - 1; + int copy_len = 1; + + for (int i = 0; i < MAX_HOPS - 1; i++) { + memmove(&failonion->attr_data->truncated_hmac[dest_index * TRUNC_HMAC_LEN], + &failonion->attr_data->truncated_hmac[src_index * TRUNC_HMAC_LEN], + copy_len * TRUNC_HMAC_LEN); + if (i == MAX_HOPS - 2) + break; + + copy_len += 1; + src_index -= copy_len + 1; + dest_index -= copy_len; + } + } + u8 *hold_times_be = tal_arr(failonion, u8, 0); + towire_u32(&hold_times_be, hold_times); + memcpy(failonion->attr_data->htlc_hold_time, hold_times_be, HOLD_TIME_LEN); + add_hmacs_to_attribution_data(failonion, shared_secret); +} + +static u8 *verify_attr_data(struct onionreply *reply, + int pos, + const struct secret *shared_secret) +{ + struct secret um_key; + struct hmac hmac; + struct crypto_auth_hmacsha256_state state; + u8 expected_hmac[4], actual_hmac[4]; + + subkey_from_hmac("um", shared_secret, &um_key); + + hmac_start(&state, um_key.data, sizeof(um_key.data)); + hmac_update(&state, reply->contents, tal_bytelen(reply->contents)); + hmac_update(&state, reply->attr_data->htlc_hold_time, (pos + 1) * HOLD_TIME_LEN); + write_downstream_hmacs(&state, pos, reply->attr_data->truncated_hmac); + hmac_done(&state, &hmac); + memcpy(expected_hmac, hmac.bytes, TRUNC_HMAC_LEN); + + /* Compare with actual index. */ + int hmac_idx = MAX_HOPS - pos - 1; + memcpy(actual_hmac, reply->attr_data->truncated_hmac + hmac_idx * TRUNC_HMAC_LEN, TRUNC_HMAC_LEN); + + if (memcmp(actual_hmac, expected_hmac, 4) != 0) { + return NULL; + } + + return tal_dup_arr(reply, u8, reply->attr_data->htlc_hold_time, 4, 0); +} + u8 *unwrap_onionreply(const tal_t *ctx, const struct secret *shared_secrets, const int numhops, @@ -810,9 +911,9 @@ u8 *unwrap_onionreply(const tal_t *ctx, const u8 *cursor; size_t max; u16 msglen; - - r = new_onionreply(tmpctx, reply->contents); + r = new_onionreply(tmpctx, reply->contents, reply->attr_data); *origin_index = -1; + int attr_hop_count = numhops > 20 ? 20 : numhops; for (int i = 0; i < numhops; i++) { struct secret key; @@ -829,6 +930,36 @@ u8 *unwrap_onionreply(const tal_t *ctx, cursor = r->contents; max = tal_count(r->contents); + /* Check attribution error HMACs, If present. */ + if (r->attr_data) { + if (i < attr_hop_count) { + int pos = attr_hop_count - i - 1; + u8 *hop_time = verify_attr_data(r, pos, &shared_secrets[i]); + if (hop_time) { + /* Shift Left */ + memmove(r->attr_data->htlc_hold_time, &r->attr_data->htlc_hold_time[HOLD_TIME_LEN], HOLD_TIME_LEN * (MAX_HOPS - 1)); + + int src_index = MAX_HOPS; + int dest_index = 1; + int copy_len = MAX_HOPS - 1; + + for (int i = 0; i < MAX_HOPS - 1; i++) { + memmove(&r->attr_data->truncated_hmac[dest_index * TRUNC_HMAC_LEN], + &r->attr_data->truncated_hmac[src_index * TRUNC_HMAC_LEN], + copy_len * TRUNC_HMAC_LEN); + + src_index += copy_len; + dest_index += copy_len + 1; + copy_len -= 1; + } + // printf("Hop time at position %d: %s \n", i, tal_hexstr(tmpctx, hop_time, 4)); + } else { + /* FIXME: Add logging */ + // printf("Invalid HMAC at pos: %d", i); + } + } + } + fromwire_hmac(&cursor, &max, &hmac); /* Too short. */ if (!cursor) diff --git a/common/sphinx.h b/common/sphinx.h index b559d61a85e9..c4dffa9b332e 100644 --- a/common/sphinx.h +++ b/common/sphinx.h @@ -18,6 +18,11 @@ struct node_id; #define ROUTING_INFO_SIZE 1300 #define TOTAL_PACKET_SIZE(payload) (VERSION_SIZE + PUBKEY_SIZE + (payload) + HMAC_SIZE) +#define HOLD_TIME_LEN 4 +#define MAX_HOPS 20 +#define HMAC_COUNT 210 +#define TRUNC_HMAC_LEN 4 + struct onionpacket { /* Cleartext information */ u8 version; @@ -241,6 +246,7 @@ struct onionpacket *sphinx_decompress(const tal_t *ctx, const struct sphinx_compressed_onion *src, const struct secret *shared_secret); +void update_attributable_data(struct onionreply *failonion, u32 hold_times, struct secret *shared_secret); /** * Use ECDH to generate a shared secret from a privkey and a pubkey. * diff --git a/common/test/run-blindedpath_onion.c b/common/test/run-blindedpath_onion.c index fc6d015cc4d2..eb3370623c99 100644 --- a/common/test/run-blindedpath_onion.c +++ b/common/test/run-blindedpath_onion.c @@ -76,7 +76,7 @@ void fromwire_sciddir_or_pubkey(const u8 **cursor UNNEEDED, size_t *max UNNEEDED struct sciddir_or_pubkey *sciddpk UNNEEDED) { fprintf(stderr, "fromwire_sciddir_or_pubkey called!\n"); abort(); } /* Generated stub for new_onionreply */ -struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED) +struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED, const struct attribution_data *attr_data TAKES UNNEEDED) { fprintf(stderr, "new_onionreply 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/common/test/run-onion-message-test.c b/common/test/run-onion-message-test.c index 7dba2c5cc2c6..6465f0a84b93 100644 --- a/common/test/run-onion-message-test.c +++ b/common/test/run-onion-message-test.c @@ -31,7 +31,7 @@ bool fromwire_channel_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, void fromwire_node_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct node_id *id UNNEEDED) { fprintf(stderr, "fromwire_node_id called!\n"); abort(); } /* Generated stub for new_onionreply */ -struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED) +struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED, const struct attribution_data *attr_data TAKES UNNEEDED) { fprintf(stderr, "new_onionreply 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/common/test/run-onion-test-vector.c b/common/test/run-onion-test-vector.c index 842c96c5da47..a80651508806 100644 --- a/common/test/run-onion-test-vector.c +++ b/common/test/run-onion-test-vector.c @@ -86,7 +86,7 @@ bool fromwire_tlv(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, bool mvt_tag_parse(const char *buf UNNEEDED, size_t len UNNEEDED, enum mvt_tag *tag UNNEEDED) { fprintf(stderr, "mvt_tag_parse called!\n"); abort(); } /* Generated stub for new_onionreply */ -struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED) +struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED, const struct attribution_data *attr_data TAKES UNNEEDED) { fprintf(stderr, "new_onionreply called!\n"); abort(); } /* Generated stub for node_id_from_hexstr */ bool node_id_from_hexstr(const char *str UNNEEDED, size_t slen UNNEEDED, struct node_id *id UNNEEDED) diff --git a/common/test/run-route_blinding_onion_test.c b/common/test/run-route_blinding_onion_test.c index d3b26db4b65c..30c60865c480 100644 --- a/common/test/run-route_blinding_onion_test.c +++ b/common/test/run-route_blinding_onion_test.c @@ -33,7 +33,7 @@ void fromwire_sciddir_or_pubkey(const u8 **cursor UNNEEDED, size_t *max UNNEEDED bool mvt_tag_parse(const char *buf UNNEEDED, size_t len UNNEEDED, enum mvt_tag *tag UNNEEDED) { fprintf(stderr, "mvt_tag_parse called!\n"); abort(); } /* Generated stub for new_onionreply */ -struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED) +struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED, const struct attribution_data *attr_data TAKES UNNEEDED) { fprintf(stderr, "new_onionreply called!\n"); abort(); } /* Generated stub for node_id_from_hexstr */ bool node_id_from_hexstr(const char *str UNNEEDED, size_t slen UNNEEDED, struct node_id *id UNNEEDED) diff --git a/common/test/run-sphinx-xor_cipher_stream.c b/common/test/run-sphinx-xor_cipher_stream.c index 0e33ace54de4..02922e08004e 100644 --- a/common/test/run-sphinx-xor_cipher_stream.c +++ b/common/test/run-sphinx-xor_cipher_stream.c @@ -98,7 +98,7 @@ void hmac_update(crypto_auth_hmacsha256_state *state UNNEEDED, const void *src UNNEEDED, size_t slen UNNEEDED) { fprintf(stderr, "hmac_update called!\n"); abort(); } /* Generated stub for new_onionreply */ -struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED) +struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED, const struct attribution_data *attr_data TAKES UNNEEDED) { fprintf(stderr, "new_onionreply 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/common/test/run-sphinx.c b/common/test/run-sphinx.c index b44101f69e1e..82294ee49f09 100644 --- a/common/test/run-sphinx.c +++ b/common/test/run-sphinx.c @@ -233,11 +233,55 @@ static void run_unit_tests(void) assert(origin_index == 4); } +static void run_unit_tests_from_bolt(void) { + struct onionreply *reply; + u8 *oreply; + char *s = "400f0000000000000064000c3500fd84d1fd012c8080808080808080808"; + u8 *raw = tal_hexdata(tmpctx, s, strlen(s)); + int origin_index; + + /* Shared secrets we already have from the forward path */ + char *secrets[] = { + "53eb63ea8a3fec3b3cd433b85cd62a4b145e1dda09391b348c4e1cd36a03ea66", + "a6519e98832a0b179f62123b3567c106db99ee37bef036e783263602f3488fae", + "3a6b412548762f0dbccce5c7ae7bb8147d1caf9b5471c34120b30bc9c04891cc", + "21e13c2d7cfe7e18836df50872466117a295783ab8aab0e7ecc8c725503ad02d", + "b5756b9b542727dbafc6765a49488b023a725d631af688fc031217e90770c328", + }; + struct secret ss[] = { + secret_from_hex(secrets[0]), + secret_from_hex(secrets[1]), + secret_from_hex(secrets[2]), + secret_from_hex(secrets[3]), + secret_from_hex(secrets[4]) + }; + + reply = create_onionreply(tmpctx, &ss[4], raw); + update_attributable_data(reply, 1, &ss[4]); + reply = wrap_onionreply(tmpctx, &ss[4], reply); + + for (int i = 3; i >= 0; i--) { + u32 hold_time = 5 - i; + update_attributable_data(reply, hold_time, &ss[i]); + reply = wrap_onionreply(tmpctx, &ss[i], reply); + + printf("obfuscated packet %s\n", tal_hex(tmpctx, reply->contents)); + printf("obfuscated hold_times %s\n", tal_hex(tmpctx, reply->attr_data->htlc_hold_time)); + printf("obfuscated trunHMACs %s\n", tal_hex(tmpctx, reply->attr_data->truncated_hmac)); + } + + oreply = unwrap_onionreply(tmpctx, ss, 5, reply, &origin_index); + + assert(tal_arr_eq(oreply, raw)); + assert(origin_index == 4); +} + int main(int argc, char **argv) { common_setup(argv[0]); run_unit_tests(); + run_unit_tests_from_bolt(); common_shutdown(); return 0; } diff --git a/lightningd/htlc_end.c b/lightningd/htlc_end.c index 82912aeb3ee8..bcc47401e5cb 100644 --- a/lightningd/htlc_end.c +++ b/lightningd/htlc_end.c @@ -275,7 +275,8 @@ struct htlc_out *new_htlc_out(const tal_t *ctx, struct amount_msat final_msat, u64 partid, u64 groupid, - struct htlc_in *in) + struct htlc_in *in, + struct timeabs send_timestamp) { struct htlc_out *hout = tal(ctx, struct htlc_out); @@ -295,6 +296,7 @@ struct htlc_out *new_htlc_out(const tal_t *ctx, hout->failonion = NULL; hout->preimage = NULL; hout->timeout = NULL; + hout->fulfill_attr = NULL; hout->path_key = tal_dup_or_null(hout, struct pubkey, path_key); @@ -317,6 +319,7 @@ struct htlc_out *new_htlc_out(const tal_t *ctx, } hout->in = NULL; + hout->send_timestamp = send_timestamp; if (in) { htlc_out_connect_htlc_in(hout, in); diff --git a/lightningd/htlc_end.h b/lightningd/htlc_end.h index 5d1932516bf7..883cba41c049 100644 --- a/lightningd/htlc_end.h +++ b/lightningd/htlc_end.h @@ -39,6 +39,9 @@ struct htlc_in { /* Otherwise, this contains the failure message to send. */ const struct onionreply *failonion; + /* This contains attribution data for the update_fulfill_htlc message. */ + struct onionreply *fulfill_onion; + /* If they fulfilled, here's the preimage. */ struct preimage *preimage; @@ -84,6 +87,9 @@ struct htlc_out { /* For a remote error. */ const struct onionreply *failonion; + /* For attribution data. */ + struct attribution_data *fulfill_attr; + /* If we fulfilled, here's the preimage. */ /* FIXME: This is basically unused, except as a bool! */ struct preimage *preimage; @@ -112,6 +118,8 @@ struct htlc_out { /* Extra tlvs that are extended to the update_add_htlc_tlvs */ struct tlv_field *extra_tlvs; + + struct timeabs send_timestamp; }; static inline const struct htlc_key *keyof_htlc_in(const struct htlc_in *in) @@ -180,7 +188,8 @@ struct htlc_out *new_htlc_out(const tal_t *ctx, struct amount_msat final_msat, u64 partid, u64 groupid, - struct htlc_in *in); + struct htlc_in *in, + struct timeabs send_timestamp); void connect_htlc_in(struct htlc_in_map *map, struct htlc_in *hin); void connect_htlc_out(struct htlc_out_map *map, struct htlc_out *hout); diff --git a/lightningd/peer_htlcs.c b/lightningd/peer_htlcs.c index b64337505a73..3b705545ba77 100644 --- a/lightningd/peer_htlcs.c +++ b/lightningd/peer_htlcs.c @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include #include #include @@ -10,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -309,17 +312,26 @@ static void fail_out_htlc(struct htlc_out *hout, const char *localfail) hout->failmsg, localfail); } else if (hout->in) { - const struct onionreply *failonion; + const struct onionreply *final_failonion; + struct onionreply *failonion; + + struct timerel delta = time_between(hout->send_timestamp, hout->in->received_time); + long total_ms = delta.ts.tv_sec * 1000 + delta.ts.tv_nsec / 1000000; + u32 hold_times = total_ms / 100; /* If we have an onion, simply copy it. */ if (hout->failonion) - failonion = hout->failonion; + failonion = dup_onionreply(hout, hout->failonion); /* Otherwise, we need to onionize this local error. */ else failonion = create_onionreply(hout, hout->in->shared_secret, hout->failmsg); - fail_in_htlc(hout->in, failonion); + + update_attributable_data(failonion, hold_times, hout->in->shared_secret); + + final_failonion = failonion; + fail_in_htlc(hout->in, final_failonion); } else { log_broken(hout->key.channel->log, "Neither origin nor in?"); } @@ -416,8 +428,20 @@ void fulfill_htlc(struct htlc_in *hin, const struct preimage *preimage) msg = towire_onchaind_known_preimage(hin, preimage); } else { struct fulfilled_htlc fulfilled_htlc; + struct attribution_data *ad = tal(hin, struct attribution_data); fulfilled_htlc.id = hin->key.id; fulfilled_htlc.payment_preimage = *preimage; + if (hin->fulfill_onion && hin->fulfill_onion->attr_data) { + ad->htlc_hold_time = tal_dup_arr(ad, u8, + hin->fulfill_onion->attr_data->htlc_hold_time, + 80, 0); + ad->truncated_hmac = tal_dup_arr(ad, u8, + hin->fulfill_onion->attr_data->truncated_hmac, + 840, 0); + fulfilled_htlc.attr_data = ad; + } else { + fulfilled_htlc.attr_data = NULL; + } msg = towire_channeld_fulfill_htlc(hin, &fulfilled_htlc); } subd_send_msg(channel->owner, take(msg)); @@ -731,7 +755,7 @@ const u8 *send_htlc_out(const tal_t *ctx, path_key, extra_tlvs, in == NULL, final_msat, - partid, groupid, in); + partid, groupid, in, time_now()); tal_add_destructor(*houtp, destroy_hout_subd_died); /* Give channel 30 seconds to commit this htlc. */ @@ -1142,7 +1166,7 @@ static bool htlc_accepted_hook_deserialize(struct htlc_accepted_hook_payload *re " Ignoring 'failure_message'."); fail_in_htlc(hin, take(new_onionreply(NULL, - failonion))); + failonion, NULL))); return false; } if (!failmsgtok) { @@ -1639,6 +1663,16 @@ static void fulfill_our_htlc_out(struct channel *channel, struct htlc_out *hout, fmt_amount_msat(tmpctx, hout->msat)); } else { struct short_channel_id scid = channel_scid_or_local_alias(hout->key.channel); + if (hout->fulfill_attr && hout->in->shared_secret) { + struct timerel delta = time_between(hout->send_timestamp, hout->in->received_time); + long total_ms = delta.ts.tv_sec * 1000 + delta.ts.tv_nsec / 1000000; + u32 hold_times = total_ms / 100; + + u8 *contents = tal_dup_arr(tmpctx, u8, preimage->r, sizeof(*preimage), 0); + struct onionreply *reply = new_onionreply(tmpctx, contents, hout->fulfill_attr); + update_attributable_data(reply, hold_times, hout->in->shared_secret); + hout->in->fulfill_onion = dup_onionreply(hout, reply); + } fulfill_htlc(hout->in, preimage); wallet_forwarded_payment_add(ld->wallet, hout->in, FORWARD_STYLE_TLV, @@ -1665,6 +1699,11 @@ static bool peer_fulfilled_our_htlc(struct channel *channel, if (!htlc_out_update_state(channel, hout, RCVD_REMOVE_COMMIT)) return false; + if (fulfilled->attr_data) { + hout->fulfill_attr = tal_dup(hout, struct attribution_data, fulfilled->attr_data); + } else { + hout->fulfill_attr = NULL; + } fulfill_our_htlc_out(channel, hout, &fulfilled->payment_preimage); return true; } diff --git a/plugins/renepay/json.c b/plugins/renepay/json.c index e48388fb308e..383dfe24184c 100644 --- a/plugins/renepay/json.c +++ b/plugins/renepay/json.c @@ -90,7 +90,7 @@ static bool get_data_details_onionreply(struct payment_result *result, goto fail; onionreply = new_onionreply( this_ctx, - take(json_tok_bin_from_hex(this_ctx, buffer, onionreplytok))); + take(json_tok_bin_from_hex(this_ctx, buffer, onionreplytok)), NULL); assert(onionreply); /* FIXME: It seems that lightningd will unwrap top portion of the * onionreply for us before serializing it, while unwrap_onionreply will diff --git a/plugins/xpay/xpay.c b/plugins/xpay/xpay.c index 955f098d5a4a..b9feab2d674b 100644 --- a/plugins/xpay/xpay.c +++ b/plugins/xpay/xpay.c @@ -682,7 +682,7 @@ static void update_knowledge_from_error(struct command *aux_cmd, if (!tok) plugin_err(aux_cmd->plugin, "Invalid injectpaymentonion result '%.*s'", json_tok_full_len(error), json_tok_full(buf, error)); - reply = new_onionreply(tmpctx, take(json_tok_bin_from_hex(NULL, buf, tok))); + reply = new_onionreply(tmpctx, take(json_tok_bin_from_hex(NULL, buf, tok)), NULL); replymsg = unwrap_onionreply(tmpctx, attempt->shared_secrets, diff --git a/tests/fuzz/fuzz-wire-update_fail_htlc.c b/tests/fuzz/fuzz-wire-update_fail_htlc.c index ca3debf6fb54..47e2077cb864 100644 --- a/tests/fuzz/fuzz-wire-update_fail_htlc.c +++ b/tests/fuzz/fuzz-wire-update_fail_htlc.c @@ -13,14 +13,14 @@ struct update_fail_htlc { static void *encode(const tal_t *ctx, const struct update_fail_htlc *s) { - return towire_update_fail_htlc(ctx, &s->channel_id, s->id, s->reason); + return towire_update_fail_htlc(ctx, &s->channel_id, s->id, s->reason, NULL); } static struct update_fail_htlc *decode(const tal_t *ctx, const void *p) { struct update_fail_htlc *s = tal(ctx, struct update_fail_htlc); - if (fromwire_update_fail_htlc(s, p, &s->channel_id, &s->id, &s->reason)) + if (fromwire_update_fail_htlc(s, p, &s->channel_id, &s->id, &s->reason, NULL)) return s; return tal_free(s); } diff --git a/tests/fuzz/fuzz-wire-update_fulfill_htlc.c b/tests/fuzz/fuzz-wire-update_fulfill_htlc.c index ed2701187397..a399de613496 100644 --- a/tests/fuzz/fuzz-wire-update_fulfill_htlc.c +++ b/tests/fuzz/fuzz-wire-update_fulfill_htlc.c @@ -12,15 +12,15 @@ struct update_fulfill_htlc { static void *encode(const tal_t *ctx, const struct update_fulfill_htlc *s) { return towire_update_fulfill_htlc(ctx, &s->channel_id, s->id, - &s->payment_preimage); + &s->payment_preimage, NULL); } static struct update_fulfill_htlc *decode(const tal_t *ctx, const void *p) { struct update_fulfill_htlc *s = tal(ctx, struct update_fulfill_htlc); - if (fromwire_update_fulfill_htlc(p, &s->channel_id, &s->id, - &s->payment_preimage)) + if (fromwire_update_fulfill_htlc(s, p, &s->channel_id, &s->id, + &s->payment_preimage, NULL)) return s; return tal_free(s); } diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c index 339a21867e82..c237899af3a8 100644 --- a/wallet/test/run-wallet.c +++ b/wallet/test/run-wallet.c @@ -1812,7 +1812,7 @@ static bool test_htlc_crud(struct lightningd *ld, const tal_t *ctx, bool bip86) CHECK_MSG( transaction_wrap(w->db, wallet_htlc_update(w, in.dbid, SENT_REMOVE_HTLC, &payment_key, 0, 0, NULL, NULL, &we_filled, in.key.id, in.key.channel, REMOTE, &in.payment_hash, in.cltv_expiry, in.msat)), "Update HTLC with payment_key failed"); - onionreply = new_onionreply(tmpctx, tal_arrz(tmpctx, u8, 100)); + onionreply = new_onionreply(tmpctx, tal_arrz(tmpctx, u8, 100), NULL); CHECK_MSG( transaction_wrap(w->db, wallet_htlc_update(w, in.dbid, SENT_REMOVE_HTLC, NULL, 0, 0, onionreply, NULL, &we_filled, in.key.id, in.key.channel, REMOTE, &in.payment_hash, in.cltv_expiry, in.msat)), "Update HTLC with failonion failed"); diff --git a/wire/peer_wire.csv b/wire/peer_wire.csv index 4b01c56836cc..6df8b6a1b6d1 100644 --- a/wire/peer_wire.csv +++ b/wire/peer_wire.csv @@ -286,11 +286,17 @@ msgtype,update_fulfill_htlc,130 msgdata,update_fulfill_htlc,channel_id,channel_id, msgdata,update_fulfill_htlc,id,u64, msgdata,update_fulfill_htlc,payment_preimage,byte,32 +tlvtype,update_fulfill_htlc_tlvs,attribution_data,1 +tlvdata,update_fulfill_htlc_tlvs,attribution_data,htlc_hold_times,u8,80 +tlvdata,update_fulfill_htlc_tlvs,attribution_data,truncated_hmacs,u8,840 msgtype,update_fail_htlc,131 msgdata,update_fail_htlc,channel_id,channel_id, msgdata,update_fail_htlc,id,u64, msgdata,update_fail_htlc,len,u16, msgdata,update_fail_htlc,reason,byte,len +tlvtype,update_fail_htlc_tlvs,attribution_data,1 +tlvdata,update_fail_htlc_tlvs,attribution_data,htlc_hold_times,u8,80 +tlvdata,update_fail_htlc_tlvs,attribution_data,truncated_hmacs,u8,840 msgtype,update_fail_malformed_htlc,135 msgdata,update_fail_malformed_htlc,channel_id,channel_id, msgdata,update_fail_malformed_htlc,id,u64, diff --git a/wire/test/run-peer-wire.c b/wire/test/run-peer-wire.c index cc688645673b..1814e0ba7527 100644 --- a/wire/test/run-peer-wire.c +++ b/wire/test/run-peer-wire.c @@ -210,6 +210,8 @@ struct msg_update_fail_htlc { struct channel_id channel_id; u64 id; u8 *reason; + + struct tlv_update_fail_htlc_tlvs *tlvs; }; struct msg_channel_announcement { secp256k1_ecdsa_signature node_signature_1; @@ -496,7 +498,8 @@ static void *towire_struct_update_fail_htlc(const tal_t *ctx, return towire_update_fail_htlc(ctx, &s->channel_id, s->id, - s->reason); + s->reason, + s->tlvs); } static struct msg_update_fail_htlc *fromwire_struct_update_fail_htlc(const tal_t *ctx, const void *p) @@ -506,7 +509,8 @@ static struct msg_update_fail_htlc *fromwire_struct_update_fail_htlc(const tal_t if (!fromwire_update_fail_htlc(ctx, p, &s->channel_id, &s->id, - &s->reason)) + &s->reason, + &s->tlvs)) return tal_free(s); return s; @@ -518,17 +522,19 @@ static void *towire_struct_update_fulfill_htlc(const tal_t *ctx, return towire_update_fulfill_htlc(ctx, &s->channel_id, s->id, - &s->payment_preimage); + &s->payment_preimage, + NULL); } static struct msg_update_fulfill_htlc *fromwire_struct_update_fulfill_htlc(const tal_t *ctx, const void *p) { struct msg_update_fulfill_htlc *s = tal(ctx, struct msg_update_fulfill_htlc); - if (fromwire_update_fulfill_htlc(p, + if (fromwire_update_fulfill_htlc(ctx, p, &s->channel_id, &s->id, - &s->payment_preimage)) + &s->payment_preimage, + NULL)) return s; return tal_free(s); } @@ -792,12 +798,19 @@ static bool announcement_signatures_eq(const struct msg_announcement_signatures return eq_upto(a, b, short_channel_id) && short_channel_id_eq(a->short_channel_id, b->short_channel_id); } +static bool tlv_update_fail_htlc_eq(const struct tlv_update_fail_htlc_tlvs_attribution_data *a, + const struct tlv_update_fail_htlc_tlvs_attribution_data *b) +{ + return eq_field(a, b, htlc_hold_times) + && eq_field(a, b, truncated_hmacs); +} static bool update_fail_htlc_eq(const struct msg_update_fail_htlc *a, const struct msg_update_fail_htlc *b) { return eq_with(a, b, id) - && eq_var(a, b, reason); + && eq_var(a, b, reason) + && eq_tlv(a, b, attribution_data, tlv_update_fail_htlc_eq); } static bool commitment_signed_eq(const struct msg_commitment_signed *a, @@ -1016,12 +1029,15 @@ int main(int argc, char *argv[]) memset(&ufh, 2, sizeof(ufh)); ufh.reason = tal_arr(ctx, u8, 2); - memset(ufh.reason, 2, 2); + ufh.tlvs = tlv_update_fail_htlc_tlvs_new(tmpctx); + ufh.tlvs->attribution_data = tal(ctx, struct tlv_update_fail_htlc_tlvs_attribution_data); + memset(ufh.tlvs->attribution_data, 3, sizeof(struct tlv_update_fail_htlc_tlvs_attribution_data)); + memset(ufh.reason, 2, 2); msg = towire_struct_update_fail_htlc(ctx, &ufh); ufh2 = fromwire_struct_update_fail_htlc(ctx, msg); assert(update_fail_htlc_eq(&ufh, ufh2)); - test_corruption(&ufh, ufh2, update_fail_htlc); + test_corruption_tlv(&ufh, ufh2, update_fail_htlc); memset(&cs, 2, sizeof(cs)); cs.htlc_signature = tal_arr(ctx, secp256k1_ecdsa_signature, 2);