From 01a47720c3472237b3000110c209b88bf095827d Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 9 Nov 2022 12:00:10 +1030 Subject: [PATCH] plugins/libplugin-pay: hack in blinded path support. We simply take the first one, and route to the start of that. Then we append the blinded path to the onion construction. Signed-off-by: Rusty Russell --- plugins/Makefile | 4 +- plugins/keysend.c | 2 + plugins/libplugin-pay.c | 61 ++++++++++++++++++++++++++----- plugins/libplugin-pay.h | 6 +++ plugins/pay.c | 50 +++++++++++++++++++++++-- plugins/test/run-route-overlong.c | 6 +++ 6 files changed, 114 insertions(+), 15 deletions(-) diff --git a/plugins/Makefile b/plugins/Makefile index e6c703869..293965180 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -172,7 +172,7 @@ ALL_PROGRAMS += $(C_PLUGINS) # Make all plugins depend on all plugin headers, for simplicity. $(PLUGIN_ALL_OBJS): $(PLUGIN_ALL_HEADER) -plugins/pay: $(PLUGIN_PAY_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_PAY_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) common/gossmap.o common/fp16.o common/route.o common/dijkstra.o common/bolt12.o common/bolt12_merkle.o wire/bolt12$(EXP)_wiregen.o bitcoin/block.o +plugins/pay: $(PLUGIN_PAY_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_PAY_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) common/gossmap.o common/fp16.o common/route.o common/dijkstra.o common/bolt12.o common/bolt12_merkle.o wire/bolt12$(EXP)_wiregen.o bitcoin/block.o common/blindedpay.o common/blindedpath.o common/hmac.o common/blinding.o common/onion_encode.o plugins/autoclean: $(PLUGIN_AUTOCLEAN_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) @@ -188,7 +188,7 @@ plugins/txprepare: $(PLUGIN_TXPREPARE_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_O plugins/bcli: $(PLUGIN_BCLI_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) -plugins/keysend: wire/tlvstream.o wire/onion$(EXP)_wiregen.o $(PLUGIN_KEYSEND_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_PAY_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) common/gossmap.o common/fp16.o common/route.o common/dijkstra.o +plugins/keysend: wire/tlvstream.o wire/onion$(EXP)_wiregen.o $(PLUGIN_KEYSEND_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_PAY_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) common/gossmap.o common/fp16.o common/route.o common/dijkstra.o common/blindedpay.o common/blindedpath.o common/hmac.o common/blinding.o common/onion_encode.o $(PLUGIN_KEYSEND_OBJS): $(PLUGIN_PAY_LIB_HEADER) plugins/spenderp: bitcoin/block.o bitcoin/preimage.o bitcoin/psbt.o common/psbt_open.o wire/peer${EXP}_wiregen.o $(PLUGIN_SPENDER_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) diff --git a/plugins/keysend.c b/plugins/keysend.c index ea806c354..c7ab8f74b 100644 --- a/plugins/keysend.c +++ b/plugins/keysend.c @@ -174,6 +174,8 @@ static struct command_result *json_keysend(struct command *cmd, const char *buf, p->destination = tal_steal(p, destination); p->payment_secret = NULL; p->payment_metadata = NULL; + p->blindedpath = NULL; + p->blindedpay = NULL; p->amount = *msat; p->routes = tal_steal(p, hints); // 22 is the Rust-Lightning default and the highest minimum we know of. diff --git a/plugins/libplugin-pay.c b/plugins/libplugin-pay.c index 1602b8a8e..5428270aa 100644 --- a/plugins/libplugin-pay.c +++ b/plugins/libplugin-pay.c @@ -1,6 +1,7 @@ #include "config.h" #include #include +#include #include #include #include @@ -1662,6 +1663,33 @@ static void payment_add_hop_onion_payload(struct payment *p, } } +static void payment_add_blindedpath(const tal_t *ctx, + struct createonion_hop *hops, + const struct blinded_path *bpath, + struct amount_msat final_amt, + u32 final_cltv) +{ + /* It's a bit of a weird API for us, so we convert it back to + * the struct tlv_tlv_payload */ + u8 **tlvs = blinded_onion_hops(tmpctx, final_amt, final_cltv, bpath); + + for (size_t i = 0; i < tal_count(tlvs); i++) { + const u8 *cursor = tlvs[i]; + size_t max = tal_bytelen(tlvs[i]); + /* First one has to use real node_id */ + if (i == 0) + node_id_from_pubkey(&hops[i].pubkey, + &bpath->first_node_id); + else + node_id_from_pubkey(&hops[i].pubkey, + &bpath->path[i]->blinded_node_id); + + /* Length is prepended, discard that first! */ + fromwire_bigsize(&cursor, &max); + hops[i].tlv_payload = fromwire_tlv_tlv_payload(ctx, &cursor, &max); + } +} + static void payment_compute_onion_payloads(struct payment *p) { struct createonion_request *cr; @@ -1690,7 +1718,9 @@ static void payment_compute_onion_payloads(struct payment *p) cr->assocdata = tal_arr(cr, u8, 0); towire_sha256(&cr->assocdata, p->payment_hash); cr->session_key = NULL; - cr->hops = tal_arr(cr, struct createonion_hop, tal_count(p->route)); + cr->hops = tal_arr(cr, struct createonion_hop, + tal_count(p->route) + + (root->blindedpath ? tal_count(root->blindedpath->path) - 1: 0)); /* Non-final hops */ for (size_t i = 0; i < hopcount - 1; i++) { @@ -1704,14 +1734,27 @@ static void payment_compute_onion_payloads(struct payment *p) &p->route[i].scid)); } - /* Final hop */ - payment_add_hop_onion_payload( - p, &cr->hops[hopcount - 1], &p->route[hopcount - 1], - &p->route[hopcount - 1], true, - root->payment_secret, root->payment_metadata); - tal_append_fmt(&routetxt, "%s", - type_to_string(tmpctx, struct short_channel_id, - &p->route[hopcount - 1].scid)); + /* If we're headed to a blinded path, connect that now. */ + if (root->blindedpath) { + payment_add_blindedpath(cr->hops, cr->hops + hopcount - 1, + root->blindedpath, + root->blindedfinalamount, + root->blindedfinalcltv); + tal_append_fmt(&routetxt, "%s -> blinded path (%zu hops)", + type_to_string(tmpctx, struct short_channel_id, + &p->route[hopcount-1].scid), + tal_count(root->blindedpath->path)); + } else { + /* Final hop */ + payment_add_hop_onion_payload( + p, &cr->hops[hopcount - 1], &p->route[hopcount - 1], + &p->route[hopcount - 1], true, + root->payment_secret, + root->payment_metadata); + tal_append_fmt(&routetxt, "%s", + type_to_string(tmpctx, struct short_channel_id, + &p->route[hopcount - 1].scid)); + } paymod_log(p, LOG_DBG, "Created outgoing onion for route: %s", routetxt); diff --git a/plugins/libplugin-pay.h b/plugins/libplugin-pay.h index 8ec8ec07a..c0898483e 100644 --- a/plugins/libplugin-pay.h +++ b/plugins/libplugin-pay.h @@ -188,6 +188,12 @@ struct payment { /* Payment metadata, from the invoice if any. */ u8 *payment_metadata; + /* Blinded path (for bolt12) */ + struct blinded_path *blindedpath; + struct blinded_payinfo *blindedpay; + struct amount_msat blindedfinalamount; + u32 blindedfinalcltv; + u64 groupid; u32 partid; u32 next_partid; diff --git a/plugins/pay.c b/plugins/pay.c index 55ddba0ef..a5149acf9 100644 --- a/plugins/pay.c +++ b/plugins/pay.c @@ -1024,6 +1024,9 @@ static struct command_result *json_pay(struct command *cmd, p = payment_new(cmd, cmd, NULL /* No parent */, paymod_mods); p->invstring = tal_steal(p, b11str); p->description = tal_steal(p, description); + /* Overridded by bolt12 if present */ + p->blindedpath = NULL; + p->blindedpay = NULL; if (!bolt12_has_prefix(b11str)) { b11 = @@ -1085,7 +1088,9 @@ static struct command_result *json_pay(struct command *cmd, return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "experimental-offers disabled"); - p->features = tal_steal(p, b12->features); + /* FIXME: We disable MPP for now */ + /* p->features = tal_steal(p, b12->features); */ + p->features = NULL; if (!b12->node_id) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, @@ -1109,8 +1114,33 @@ static struct command_result *json_pay(struct command *cmd, return command_fail( cmd, JSONRPC2_INVALID_PARAMS, "recurring invoice requires a label"); - /* FIXME payment_secret should be signature! */ - { + + /* BOLT-offers #12: + * - MUST reject the invoice if `blindedpay` is not present. + */ + /* FIXME: We allow this for now. */ + + if (tal_count(b12->paths) != 0) { + /* BOLT-offers #12: - MUST reject the invoice if + * `blindedpay` does not contain exactly one + * `blinded_payinfo` per `blinded_path`. + */ + if (tal_count(b12->paths) != tal_count(b12->blindedpay)) { + return command_fail( + cmd, JSONRPC2_INVALID_PARAMS, + "Wrong blinding info: %zu paths, %zu payinfo", + tal_count(b12->paths), + tal_count(b12->blindedpay)); + } + + /* FIXME: do MPP across these! We choose first one. */ + p->blindedpath = tal_steal(p, b12->paths[0]); + p->blindedpay = tal_steal(p, b12->blindedpay[0]); + + /* Set destination to introduction point */ + node_id_from_pubkey(p->destination, &p->blindedpath->first_node_id); + } else { + /* FIXME payment_secret should be signature! */ struct sha256 merkle; p->payment_secret = tal(p, struct secret); @@ -1121,7 +1151,6 @@ static struct command_result *json_pay(struct command *cmd, } p->payment_metadata = NULL; p->routes = NULL; - /* FIXME: paths! */ if (b12->cltv) p->min_final_cltv_expiry = *b12->cltv; else @@ -1161,6 +1190,19 @@ static struct command_result *json_pay(struct command *cmd, p->amount = *msat; } + /* We replace real final values if we're using a blinded path */ + if (p->blindedpath) { + p->blindedfinalcltv = p->min_final_cltv_expiry; + p->blindedfinalamount = p->amount; + + p->min_final_cltv_expiry += p->blindedpay->cltv_expiry_delta; + if (!amount_msat_add_fee(&p->amount, + p->blindedpay->fee_base_msat, + p->blindedpay->fee_proportional_millionths)) + return command_fail(cmd, PAY_ROUTE_TOO_EXPENSIVE, + "This payment blinded path fee overflows!"); + } + if (node_id_eq(&my_id, p->destination)) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "This payment is destined for ourselves. " diff --git a/plugins/test/run-route-overlong.c b/plugins/test/run-route-overlong.c index 870356fa3..ba87bb1cd 100644 --- a/plugins/test/run-route-overlong.c +++ b/plugins/test/run-route-overlong.c @@ -8,6 +8,12 @@ #include /* AUTOGENERATED MOCKS START */ +/* Generated stub for blinded_onion_hops */ +u8 **blinded_onion_hops(const tal_t *ctx UNNEEDED, + struct amount_msat final_amount UNNEEDED, + u32 final_cltv UNNEEDED, + const struct blinded_path *path UNNEEDED) +{ fprintf(stderr, "blinded_onion_hops called!\n"); abort(); } /* Generated stub for command_finished */ struct command_result *command_finished(struct command *cmd UNNEEDED, struct json_stream *response UNNEEDED) { fprintf(stderr, "command_finished called!\n"); abort(); }