Skip to content

Commit

Permalink
offers: handle scid in blinded reply path first_node_id field.
Browse files Browse the repository at this point in the history
Signed-off-by: Rusty Russell <[email protected]>
Changelog-EXPERIMENTAL: offers: we now understand blinded paths which use a short-channel-id(+direction) as entry point.
  • Loading branch information
rustyrussell committed Apr 12, 2024
1 parent 0a13f4a commit 901167f
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 10 deletions.
2 changes: 1 addition & 1 deletion plugins/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ $(PLUGIN_KEYSEND_OBJS): $(PLUGIN_PAY_LIB_HEADER)

plugins/spenderp: bitcoin/block.o bitcoin/preimage.o bitcoin/psbt.o common/psbt_open.o common/json_channel_type.o common/channel_type.o common/features.o wire/peer_wiregen.o $(PLUGIN_SPENDER_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS)

plugins/offers: $(PLUGIN_OFFERS_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) common/addr.o common/bolt12.o common/bolt12_merkle.o common/bolt11_json.o common/iso4217.o $(WIRE_OBJS) $(WIRE_BOLT12_OBJS) bitcoin/block.o common/channel_id.o bitcoin/preimage.o common/blindedpath.o common/invoice_path_id.o common/blinding.o common/hmac.o common/json_blinded_path.o $(JSMN_OBJS)
plugins/offers: $(PLUGIN_OFFERS_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) common/addr.o common/bolt12.o common/bolt12_merkle.o common/bolt11_json.o common/iso4217.o $(WIRE_OBJS) $(WIRE_BOLT12_OBJS) bitcoin/block.o common/channel_id.o bitcoin/preimage.o common/blindedpath.o common/invoice_path_id.o common/blinding.o common/hmac.o common/json_blinded_path.o common/gossmap.o common/fp16.o $(JSMN_OBJS)

plugins/fetchinvoice: $(PLUGIN_FETCHINVOICE_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) common/bolt12.o common/bolt12_merkle.o common/iso4217.o $(WIRE_OBJS) $(WIRE_BOLT12_OBJS) bitcoin/block.o common/channel_id.o bitcoin/preimage.o $(JSMN_OBJS) common/gossmap.o common/fp16.o common/dijkstra.o common/route.o common/blindedpath.o common/hmac.o common/blinding.o common/gossmods_listpeerchannels.o

Expand Down
29 changes: 27 additions & 2 deletions plugins/fetchinvoice.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ struct sent {
/* Path to use (including self) */
struct pubkey *path;

/* When creating blinded return path, use scid not pubkey for intro node. */
struct short_channel_id_dir *dev_path_use_scidd;

/* The invreq we sent, OR the invoice we sent */
struct tlv_invoice_request *invreq;

Expand Down Expand Up @@ -597,6 +600,7 @@ send_modern_message(struct command *cmd,
static struct blinded_path *blinded_path(const tal_t *ctx,
struct command *cmd,
const struct pubkey *ids,
const struct short_channel_id_dir *first_scidd,
const struct secret *pathsecret)
{
struct privkey first_blinding, blinding_iter;
Expand All @@ -608,7 +612,10 @@ static struct blinded_path *blinded_path(const tal_t *ctx,
nhops = tal_count(ids);

assert(nhops > 0);
sciddir_or_pubkey_from_pubkey(&path->first_node_id, &ids[0]);
if (first_scidd)
sciddir_or_pubkey_from_scidd(&path->first_node_id, first_scidd);
else
sciddir_or_pubkey_from_pubkey(&path->first_node_id, &ids[0]);
assert(pubkey_eq(&ids[nhops-1], &local_id));

randombytes_buf(&first_blinding, sizeof(first_blinding));
Expand Down Expand Up @@ -673,7 +680,8 @@ static struct command_result *make_reply_path(struct command *cmd,
for (int i = nhops - 2; i >= 0; i--)
ids[nhops - 2 - i] = sending->sent->path[i];

rpath = blinded_path(cmd, cmd, ids, sending->sent->reply_secret);
rpath = blinded_path(cmd, cmd, ids, sending->sent->dev_path_use_scidd,
sending->sent->reply_secret);
return send_modern_message(cmd, rpath, sending);
}

Expand Down Expand Up @@ -927,6 +935,22 @@ static struct command_result *invreq_done(struct command *cmd,
return send_outreq(cmd->plugin, req);
}

static struct command_result *param_dev_scidd(struct command *cmd, const char *name,
const char *buffer, const jsmntok_t *tok,
struct short_channel_id_dir **scidd)
{
if (!plugin_developer_mode(cmd->plugin))
return command_fail_badparam(cmd, name, buffer, tok,
"not available outside --developer mode");

*scidd = tal(cmd, struct short_channel_id_dir);
if (short_channel_id_dir_from_str(buffer + tok->start, tok->end - tok->start, *scidd))
return NULL;

return command_fail_badparam(cmd, name, buffer, tok,
"should be a short_channel_id of form NxNxN/dir");
}

/* Fetches an invoice for this offer, and makes sure it corresponds. */
static struct command_result *json_fetchinvoice(struct command *cmd,
const char *buffer,
Expand All @@ -950,6 +974,7 @@ static struct command_result *json_fetchinvoice(struct command *cmd,
p_opt("recurrence_label", param_string, &rec_label),
p_opt_def("timeout", param_number, &timeout, 60),
p_opt("payer_note", param_string, &payer_note),
p_opt("dev_path_use_scidd", param_dev_scidd, &sent->dev_path_use_scidd),
NULL))
return command_param_failed();

Expand Down
69 changes: 62 additions & 7 deletions plugins/offers.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
#include <common/bolt11.h>
#include <common/bolt11_json.h>
#include <common/bolt12_merkle.h>
#include <common/gossmap.h>
#include <common/invoice_path_id.h>
#include <common/iso4217.h>
#include <common/json_blinded_path.h>
#include <common/json_param.h>
#include <common/json_stream.h>
#include <common/memleak.h>
#include <errno.h>
#include <plugins/offers.h>
#include <plugins/offers_inv_hook.h>
#include <plugins/offers_invreq_hook.h>
Expand All @@ -29,6 +32,32 @@ u32 blockheight;
u16 cltv_final;
bool offers_enabled;
struct secret invoicesecret_base;
static struct gossmap *global_gossmap;

static void init_gossmap(struct plugin *plugin)
{
size_t num_cupdates_rejected;
global_gossmap
= notleak_with_children(gossmap_load(plugin,
GOSSIP_STORE_FILENAME,
&num_cupdates_rejected));
if (!global_gossmap)
plugin_err(plugin, "Could not load gossmap %s: %s",
GOSSIP_STORE_FILENAME, strerror(errno));
if (num_cupdates_rejected)
plugin_log(plugin, LOG_DBG,
"gossmap ignored %zu channel updates",
num_cupdates_rejected);
}

static struct gossmap *get_gossmap(struct plugin *plugin)
{
if (!global_gossmap)
init_gossmap(plugin);
else
gossmap_refresh(global_gossmap, NULL);
return global_gossmap;
}

static struct command_result *finished(struct command *cmd,
const char *buf,
Expand All @@ -52,6 +81,32 @@ static struct command_result *sendonionmessage_error(struct command *cmd,
return command_hook_success(cmd);
}

/* So, you gave us a reply scid? Let's do the lookup then! And no,
* we won't accept private channels, just public ones.
*/
static bool convert_to_scidd(struct command *cmd,
struct sciddir_or_pubkey *sciddpk)
{
struct gossmap *gossmap = get_gossmap(cmd->plugin);
struct gossmap_chan *chan;
struct gossmap_node *node;
struct node_id id;

chan = gossmap_find_chan(gossmap, &sciddpk->scidd.scid);
if (!chan)
return false;

node = gossmap_nth_node(gossmap, chan, sciddpk->scidd.dir);
gossmap_node_get_id(gossmap, node, &id);
if (!sciddir_or_pubkey_from_node_id(sciddpk, &id)) {
plugin_log(cmd->plugin, LOG_BROKEN,
"Could not convert node %s to pubkey?",
fmt_node_id(tmpctx, &id));
return false;
}
return true;
}

struct command_result *
send_onion_reply(struct command *cmd,
struct blinded_path *reply_path,
Expand All @@ -60,6 +115,13 @@ send_onion_reply(struct command *cmd,
struct out_req *req;
size_t nhops;

if (!reply_path->first_node_id.is_pubkey
&& !convert_to_scidd(cmd, &reply_path->first_node_id)) {
plugin_log(cmd->plugin, LOG_INFORM, "Unknown reply scid %s: cannot send reply",
fmt_short_channel_id_dir(tmpctx, &reply_path->first_node_id.scidd));
return command_hook_success(cmd);
}

req = jsonrpc_request_start(cmd->plugin, cmd, "sendonionmessage",
finished, sendonionmessage_error, NULL);

Expand Down Expand Up @@ -111,13 +173,6 @@ static struct command_result *onion_message_modern_call(struct command *cmd,
plugin_err(cmd->plugin, "Invalid reply path %.*s?",
json_tok_full_len(replytok),
json_tok_full(buf, replytok));

/* FIXME: support this! */
if (!reply_path->first_node_id.is_pubkey) {
plugin_log(cmd->plugin, LOG_DBG,
"reply_blindedpath uses scid");
return command_hook_success(cmd);
}
}

invreqtok = json_get_member(buf, om, "invoice_request");
Expand Down
13 changes: 13 additions & 0 deletions tests/test_pay.py
Original file line number Diff line number Diff line change
Expand Up @@ -5588,3 +5588,16 @@ def test_pay_partial_msat(node_factory, executor):

l1pay.result(TIMEOUT)
l3pay.result(TIMEOUT)


def test_blinded_reply_path_scid(node_factory):
"""Check that we handle a blinded path which begins with a scid instead of a nodeid"""
l1, l2 = node_factory.line_graph(2, wait_for_announce=True,
opts={'experimental-offers': None})
offer = l2.rpc.offer(amount='2msat', description='test_blinded_reply_path_scid')

chan = only_one(l1.rpc.listpeerchannels()['channels'])
scidd = "{}/{}".format(chan['short_channel_id'], chan['direction'])
inv = l1.rpc.fetchinvoice(offer=offer['bolt12'], dev_path_use_scidd=scidd)['invoice']

l1.rpc.pay(inv)

0 comments on commit 901167f

Please sign in to comment.