diff --git a/plugins/Makefile b/plugins/Makefile index 293965180..4bfee3e1e 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -193,7 +193,7 @@ $(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) -plugins/offers: $(PLUGIN_OFFERS_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) 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 $(JSMN_OBJS) +plugins/offers: $(PLUGIN_OFFERS_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) 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 $(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 diff --git a/plugins/offers.c b/plugins/offers.c index f781cfd4b..ebd7c8d64 100644 --- a/plugins/offers.c +++ b/plugins/offers.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,7 @@ struct pubkey id; u32 blockheight; u16 cltv_final; bool offers_enabled; +struct secret invoicesecret_base; static struct command_result *finished(struct command *cmd, const char *buf, @@ -951,6 +953,11 @@ static const char *init(struct plugin *p, JSON_SCAN(json_to_u16, &cltv_final), JSON_SCAN(json_to_bool, &offers_enabled)); + rpc_scan(p, "makesecret", + take(json_out_obj(NULL, "string", INVOICE_PATH_BASE_STRING)), + "{secret:%}", + JSON_SCAN(json_to_secret, &invoicesecret_base)); + return NULL; } diff --git a/plugins/offers_invreq_hook.c b/plugins/offers_invreq_hook.c index 0cf6b71fe..108764eb2 100644 --- a/plugins/offers_invreq_hook.c +++ b/plugins/offers_invreq_hook.c @@ -3,7 +3,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -210,6 +212,9 @@ static struct command_result *create_invoicereq(struct command *cmd, { struct out_req *req; + /* FIXME: We should add a real blinded path, and we *need to* + * if we don't have public channels! */ + /* Now, write invoice to db (returns the signed version) */ req = jsonrpc_request_start(cmd->plugin, cmd, "createinvoice", createinvoice_done, createinvoice_error, ir); @@ -222,6 +227,202 @@ static struct command_result *create_invoicereq(struct command *cmd, return send_outreq(cmd->plugin, req); } +/* Create and encode an enctlv */ +static u8 *create_enctlv(const tal_t *ctx, + /* in and out */ + struct privkey *blinding, + const struct pubkey *node_id, + struct short_channel_id *next_scid, + struct tlv_encrypted_data_tlv_payment_relay *payment_relay, + struct tlv_encrypted_data_tlv_payment_constraints *payment_constraints, + u8 *path_secret, + struct pubkey *node_alias) +{ + struct tlv_encrypted_data_tlv *tlv = tlv_encrypted_data_tlv_new(tmpctx); + + tlv->short_channel_id = next_scid; + tlv->path_id = path_secret; + tlv->payment_relay = payment_relay; + tlv->payment_constraints = payment_constraints; + /* FIXME: Add padding! */ + + return encrypt_tlv_encrypted_data(ctx, blinding, node_id, tlv, + blinding, node_alias); +} + +/* If we only have private channels, we need to add a blinded path to the + * invoice. We need to choose a peer who supports blinded payments, too. */ +struct chaninfo { + struct pubkey id; + struct short_channel_id scid; + struct amount_msat capacity, htlc_min, htlc_max; + u32 feebase, feeppm, cltv; + bool public; +}; + +/* FIXME: This is naive: + * - Only creates if we have no public channels. + * - Always creates a path from direct neighbor. + * - Doesn't append dummy hops. + * - Doesn't pad to length. + */ +/* (We only create if we have to, because our code doesn't handle + * making a payment if the blinded path starts with ourselves!) */ +static struct command_result *listincoming_done(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct invreq *ir) +{ + const jsmntok_t *arr, *t; + size_t i; + struct chaninfo *best = NULL; + bool any_public = false; + + arr = json_get_member(buf, result, "incoming"); + json_for_each_arr(i, t, arr) { + struct chaninfo ci; + const jsmntok_t *pftok; + u8 *features; + const char *err; + + err = json_scan(tmpctx, buf, t, + "{id:%," + "incoming_capacity_msat:%," + "htlc_min_msat:%," + "htlc_max_msat:%," + "fee_base_msat:%," + "fee_proportional_millionths:%," + "cltv_expiry_delta:%," + "short_channel_id:%," + "public:%}", + JSON_SCAN(json_to_pubkey, &ci.id), + JSON_SCAN(json_to_msat, &ci.capacity), + JSON_SCAN(json_to_msat, &ci.htlc_min), + JSON_SCAN(json_to_msat, &ci.htlc_max), + JSON_SCAN(json_to_u32, &ci.feebase), + JSON_SCAN(json_to_u32, &ci.feeppm), + JSON_SCAN(json_to_u32, &ci.cltv), + JSON_SCAN(json_to_short_channel_id, &ci.scid), + JSON_SCAN(json_to_bool, &ci.public)); + if (err) { + plugin_log(cmd->plugin, LOG_BROKEN, + "Could not parse listincoming: %s", + err); + continue; + } + + any_public |= ci.public; + + /* Not presented if there's no channel_announcement for peer: + * we could use listpeers, but if it's private we probably + * don't want to blinded route through it! */ + pftok = json_get_member(buf, t, "peer_features"); + if (!pftok) + continue; + features = json_tok_bin_from_hex(tmpctx, buf, pftok); + if (!feature_offered(features, OPT_ROUTE_BLINDING)) + continue; + + if (amount_msat_less(ci.htlc_max, amount_msat(*ir->inv->amount))) + continue; + + /* Only pick a private one if no public candidates. */ + if (!best || (!best->public && ci.public)) + best = tal_dup(tmpctx, struct chaninfo, &ci); + } + + /* If there are any public channels, don't add. */ + if (any_public) + goto done; + + /* BOLT-offers #12: + * - MUST include `invoice_paths` containing one or more paths to the node. + * - MUST specify `invoice_paths` in order of most-preferred to + * least-preferred if it has a preference. + * - MUST include `invoice_blindedpay` with exactly one `blinded_payinfo` + * for each `blinded_path` in `paths`, in order. + */ + if (!best) { + /* Note: since we don't make one, createinvoice adds a dummy. */ + plugin_log(cmd->plugin, LOG_UNUSUAL, + "No incoming channel for %s, so no blinded path", + fmt_amount_msat(tmpctx, amount_msat(*ir->inv->amount))); + } else { + struct privkey blinding; + struct tlv_encrypted_data_tlv_payment_relay relay; + struct tlv_encrypted_data_tlv_payment_constraints constraints; + struct onionmsg_hop **hops; + u32 base; + + relay.cltv_expiry_delta = best->cltv; + relay.fee_base_msat = best->feebase; + relay.fee_proportional_millionths = best->feeppm; + + /* Give them 6 blocks, plus one per 10 minutes until expiry. */ + if (ir->inv->relative_expiry) + base = blockheight + 6 + *ir->inv->relative_expiry / 600; + else + base = blockheight + 6 + 7200 / 600; + constraints.max_cltv_expiry = base + best->cltv + cltv_final; + constraints.htlc_minimum_msat = best->htlc_min.millisatoshis; /* Raw: tlv */ + + randombytes_buf(&blinding, sizeof(blinding)); + + ir->inv->paths = tal_arr(ir->inv, struct blinded_path *, 1); + ir->inv->paths[0] = tal(ir->inv->paths, struct blinded_path); + ir->inv->paths[0]->first_node_id = best->id; + if (!pubkey_from_privkey(&blinding, + &ir->inv->paths[0]->blinding)) + abort(); + hops = tal_arr(ir->inv->paths[0], struct onionmsg_hop *, 2); + ir->inv->paths[0]->path = hops; + + /* First hop is the peer */ + hops[0] = tal(hops, struct onionmsg_hop); + hops[0]->encrypted_recipient_data + = create_enctlv(hops[0], + &blinding, + &best->id, + &best->scid, + &relay, &constraints, + NULL, + &hops[0]->blinded_node_id); + /* Second hops is us (so we can identify correct use of path) */ + hops[1] = tal(hops, struct onionmsg_hop); + hops[1]->encrypted_recipient_data + = create_enctlv(hops[1], + &blinding, + &id, + NULL, NULL, NULL, + invoice_path_id(tmpctx, &invoicesecret_base, + ir->inv->payment_hash), + &hops[1]->blinded_node_id); + + /* FIXME: This should be a "normal" feerate and range. */ + ir->inv->blindedpay = tal_arr(ir->inv, struct blinded_payinfo *, 1); + ir->inv->blindedpay[0] = tal(ir->inv->blindedpay, struct blinded_payinfo); + ir->inv->blindedpay[0]->fee_base_msat = best->feebase; + ir->inv->blindedpay[0]->fee_proportional_millionths = best->feeppm; + ir->inv->blindedpay[0]->cltv_expiry_delta = best->cltv; + ir->inv->blindedpay[0]->htlc_minimum_msat = best->htlc_min; + ir->inv->blindedpay[0]->htlc_maximum_msat = best->htlc_max; + ir->inv->blindedpay[0]->features = NULL; + } + +done: + return create_invoicereq(cmd, ir); +} + +static struct command_result *add_blindedpaths(struct command *cmd, + struct invreq *ir) +{ + struct out_req *req; + + req = jsonrpc_request_start(cmd->plugin, cmd, "listincoming", + listincoming_done, listincoming_done, ir); + return send_outreq(cmd->plugin, req); +} + static struct command_result *check_period(struct command *cmd, struct invreq *ir, u64 basetime) @@ -341,7 +542,7 @@ static struct command_result *check_period(struct command *cmd, } } - return create_invoicereq(cmd, ir); + return add_blindedpaths(cmd, ir); } static struct command_result *prev_invoice_done(struct command *cmd, @@ -548,7 +749,7 @@ static struct command_result *handle_amount_and_recurrence(struct command *cmd, * request another. */ /* FIXME: Fallbacks? */ - return create_invoicereq(cmd, ir); + return add_blindedpaths(cmd, ir); } static struct command_result *currency_done(struct command *cmd, diff --git a/plugins/offers_invreq_hook.h b/plugins/offers_invreq_hook.h index edf150782..2259fe9b2 100644 --- a/plugins/offers_invreq_hook.h +++ b/plugins/offers_invreq_hook.h @@ -4,6 +4,9 @@ #include extern u16 cltv_final; +extern u32 blockheight; +extern struct secret invoicesecret_base; +extern struct pubkey id; /* We got an onionmessage with an invreq! */ struct command_result *handle_invoice_request(struct command *cmd,