common/bolt12: allow missing offer_issuer_id.

The latest spec allows this to be omitted iff there is a blinded path
and it would be made up anyway.

In that case, the key they will use to sign the invoice will be the final
blinded key in the path we use.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2024-08-01 09:33:34 +09:30
parent 2ecf5e6bd5
commit d740795139
8 changed files with 80 additions and 34 deletions

View file

@ -222,9 +222,13 @@ struct tlv_offer *offer_decode(const tal_t *ctx,
return tal_free(offer);
}
/* FIXME(vincenzopalazzo): node id can be null when we use blinded path. */
if (!offer->offer_issuer_id) {
*fail = tal_strdup(ctx, "Offer does not contain an issuer_id");
/* BOLT-offers #12:
*
* - if neither `offer_issuer_id` nor `offer_paths` are set:
* - MUST NOT respond to the offer.
*/
if (!offer->offer_issuer_id && !offer->offer_paths) {
*fail = tal_strdup(ctx, "Offer does not contain an issuer_id or paths");
return tal_free(offer);
}

View file

@ -789,8 +789,16 @@ int main(int argc, char *argv[])
well_formed &= print_offer_amount(offer->offer_chains,
offer->offer_currency,
*offer->offer_amount);
if (must_have(offer, offer_description))
if (offer->offer_description)
well_formed &= print_utf8("offer_description", offer->offer_description);
/* BOLT-offers #12:
* - if `offer_amount` is set and `offer_description` is not set:
* - MUST NOT respond to the offer.
*/
if (offer->offer_amount && !offer->offer_description) {
fprintf(stderr, "Missing offer_description (with offer_amount)\n");
well_formed = false;
}
if (offer->offer_features)
print_features("offer_features", offer->offer_features);
if (offer->offer_absolute_expiry)
@ -801,9 +809,17 @@ int main(int argc, char *argv[])
well_formed &= print_utf8("offer_issuer", offer->offer_issuer);
if (offer->offer_quantity_max)
print_u64("offer_quantity_max", *offer->offer_quantity_max);
/* FIXME: can have path instead! */
if (must_have(offer, offer_issuer_id))
if (offer->offer_issuer_id)
print_node_id("offer_issuer_id", offer->offer_issuer_id);
/* BOLT-offers #12:
*
* - if neither `offer_issuer_id` nor `offer_paths` are set:
* - MUST NOT respond to the offer.
*/
if (!offer->offer_issuer_id && !offer->offer_paths) {
fprintf(stderr, "Missing offer_issuer_id and offer_paths\n");
well_formed = false;
}
if (offer->offer_recurrence)
well_formed &= print_recurrance(offer->offer_recurrence,
offer->offer_recurrence_paywindow,

View file

@ -1767,7 +1767,7 @@ static struct command_result *json_createinvoice(struct command *cmd,
hsm_sign_b12_invoice(cmd->ld, inv);
b12enc = invoice_encode(cmd, inv);
if (inv->offer_issuer_id) {
if (inv->offer_issuer_id || inv->offer_paths) {
invoice_offer_id(inv, &offer_id);
if (wallet_offer_find(tmpctx, cmd->ld->wallet,
&offer_id, NULL, &status)) {

View file

@ -41,6 +41,9 @@ struct sent {
struct blinded_path **their_paths;
/* Direct destination (used iff no their_paths) */
struct pubkey *direct_dest;
/* Key we expect to sign the invoice: the offer_issuer_id if
* present, otherwise the end blinded path we sent to. */
struct pubkey *issuer_key;
/* When creating blinded return path, use scid not pubkey for intro node. */
struct short_channel_id_dir *dev_path_use_scidd;
@ -217,10 +220,10 @@ static struct command_result *handle_invreq_response(struct command *cmd,
}
/* BOLT-offers #12:
* - if `offer_issuer_id` is present (invoice_request for an offer):
* - MUST reject the invoice if `invoice_node_id` is not equal to `offer_issuer_id`.
* - if `offer_node_id` or `offer_paths` are present (invoice_request for an offer):
* - MUST reject the invoice if `invoice_node_id` is not equal to the public key it sent the `invoice_request` to.
*/
if (!inv->invoice_node_id || !pubkey_eq(inv->offer_issuer_id, inv->invoice_node_id)) {
if (!inv->invoice_node_id || !pubkey_eq(inv->offer_issuer_id, sent->issuer_key)) {
badfield = "invoice_node_id";
goto badinv;
}
@ -479,7 +482,7 @@ struct establishing_paths {
static const struct blinded_path *current_their_path(const struct establishing_paths *epaths)
{
if (tal_count(epaths->sent->their_paths) == 0)
if (epaths->sent->direct_dest)
return NULL;
assert(epaths->which_blinded_path < tal_count(epaths->sent->their_paths));
return epaths->sent->their_paths[epaths->which_blinded_path];
@ -521,7 +524,7 @@ static struct command_result *establish_path_fail(struct command *cmd,
const struct blinded_path *bpath = current_their_path(epaths);
/* No blinded paths? We fail to establish connection directly */
if (!bpath) {
if (epaths->sent->direct_dest) {
return command_fail(cmd, OFFER_ROUTE_NOT_FOUND,
"Failed: could not route or connect directly to %s: %s",
fmt_pubkey(tmpctx, epaths->sent->direct_dest), why);
@ -545,15 +548,23 @@ static struct command_result *try_establish(struct command *cmd,
struct establishing_paths *epaths)
{
struct pubkey target;
const struct blinded_path *bpath = current_their_path(epaths);
if (!bpath) {
if (epaths->sent->direct_dest) {
target = *epaths->sent->direct_dest;
} else {
const struct blinded_path *bpath = current_their_path(epaths);
struct sciddir_or_pubkey first = bpath->first_node_id;
if (!gossmap_scidd_pubkey(get_gossmap(cmd->plugin), &first))
return establish_path_fail(cmd, "Cannot resolve scidd", epaths);
target = first.pubkey;
/* BOLT-offers #12:
* - if `offer_issuer_id` is present (invoice_request for an offer):
* - MUST reject the invoice if `invoice_node_id` is not equal `offer_issuer_id`
* - otherwise, if `offer_paths` is present (invoice_request for an offer without id):
* - MUST reject the invoice if `invoice_node_id` is not equal to the final `blinded_node_id` it sent the `invoice_request` to.
*/
if (!epaths->sent->offer->offer_issuer_id)
epaths->sent->issuer_key = &bpath->path[tal_count(bpath->path)-1]->blinded_node_id;
}
return establish_onion_path(cmd, get_gossmap(cmd->plugin), &id, &target,
@ -788,7 +799,12 @@ struct command_result *json_fetchinvoice(struct command *cmd,
sent->wait_timeout = *timeout;
sent->their_paths = sent->offer->offer_paths;
sent->direct_dest = sent->offer->offer_issuer_id;
if (sent->their_paths)
sent->direct_dest = NULL;
else
sent->direct_dest = sent->offer->offer_issuer_id;
/* This is NULL if offer_issuer_id is missing, and set by try_establish */
sent->issuer_key = sent->offer->offer_issuer_id;
/* BOLT-offers #12:
* - SHOULD not respond to an offer if the current time is after
@ -1036,7 +1052,10 @@ static struct command_result *createinvoice_done(struct command *cmd,
* `invreq_payer_id` as the node id to send to.
*/
sent->their_paths = sent->invreq->offer_paths;
sent->direct_dest = sent->invreq->invreq_payer_id;
if (sent->their_paths)
sent->direct_dest = NULL;
else
sent->direct_dest = sent->invreq->invreq_payer_id;
payload = tlv_onionmsg_tlv_new(sent);
@ -1144,13 +1163,13 @@ static struct command_result *param_invreq(struct command *cmd,
/* Plugin handles these automatically, you shouldn't send one
* manually. */
if ((*invreq)->offer_issuer_id) {
if ((*invreq)->offer_issuer_id || (*invreq)->offer_paths) {
return command_fail_badparam(cmd, name, buffer, tok,
"This is based on an offer?");
}
/* BOLT-offers #12:
* - otherwise (no `offer_issuer_id`, not a response to our offer):
* - otherwise (no `offer_node_id` or `offer_paths`, not a response to our offer):
* - MUST fail the request if any of the following are present:
* - `offer_chains`, `offer_features` or `offer_quantity_max`.
* - MUST fail the request if `invreq_amount` is not present.
@ -1169,7 +1188,7 @@ static struct command_result *param_invreq(struct command *cmd,
"Missing invreq_amount");
/* BOLT-offers #12:
* - otherwise (no `offer_issuer_id`, not a response to our offer):
* - otherwise (no `offer_issuer_id` or `offer_paths`, not a response to our offer):
*...
* - MAY use `offer_amount` (or `offer_currency`) for informational display to user.
*/
@ -1261,6 +1280,8 @@ struct command_result *json_sendinvoice(struct command *cmd,
/* BOLT-offers #12:
* - if `offer_issuer_id` is present:
* - MUST set `invoice_node_id` to `offer_issuer_id`.
* - otherwise, if `offer_paths` is present:
* - MUST set `invoice_node_id` to the final `blinded_node_id` on the path it received the `invoice_request`
* - otherwise:
* - MUST set `invoice_node_id` to a valid public key.
*/
@ -1335,6 +1356,7 @@ struct command_result *json_dev_rawrequest(struct command *cmd,
sent->dev_reply_path = NULL;
sent->their_paths = NULL;
sent->direct_dest = node_id;
sent->issuer_key = node_id;
payload = tlv_onionmsg_tlv_new(sent);
payload->invoice_request = tal_arr(payload, u8, 0);

View file

@ -693,7 +693,6 @@ static bool json_add_offer_fields(struct json_stream *js,
json_object_end(js);
}
/* Required for offers, *not* for others! */
if (offer_issuer_id)
json_add_pubkey(js, "offer_issuer_id", offer_issuer_id);
@ -749,12 +748,12 @@ static void json_add_offer(struct json_stream *js, const struct tlv_offer *offer
offer->offer_recurrence_limit,
offer->offer_recurrence_base);
/* BOLT-offers #12:
* - if `offer_issuer_id` is not set:
* - if neither `offer_issuer_id` nor `offer_paths` are set:
* - MUST NOT respond to the offer.
*/
if (!offer->offer_issuer_id) {
if (!offer->offer_issuer_id && !offer->offer_paths) {
json_add_string(js, "warning_missing_offer_issuer_id",
"offers without a node_id are invalid");
"offers without an issuer_id or paths are invalid");
valid = false;
}
json_add_extra_fields(js, "unknown_offer_tlvs", offer->fields);
@ -890,8 +889,8 @@ static void json_add_invoice_request(struct json_stream *js,
{
bool valid = true;
/* If there's an offer_issuer_id, then there's an offer. */
if (invreq->offer_issuer_id) {
/* If there's an offer_issuer_id or offer_paths, then there's an offer. */
if (invreq->offer_issuer_id || invreq->offer_paths) {
struct sha256 offer_id;
invreq_offer_id(invreq, &offer_id);
@ -967,8 +966,8 @@ static void json_add_b12_invoice(struct json_stream *js,
{
bool valid = true;
/* If there's an offer_issuer_id, then there's an offer. */
if (invoice->offer_issuer_id) {
/* If there's an offer_issuer_id or offer_paths, then there's an offer. */
if (invoice->offer_issuer_id || invoice->offer_paths) {
struct sha256 offer_id;
invoice_offer_id(invoice, &offer_id);

View file

@ -136,6 +136,8 @@ static struct command_result *listinvreqs_done(struct command *cmd,
* - MUST reject the invoice if all fields in ranges 0 to 159 and 1000000000 to 2999999999 (inclusive) do not exactly match the `invoice_request`.
* - if `offer_issuer_id` is present (invoice_request for an offer):
* - MUST reject the invoice if `invoice_node_id` is not equal to `offer_issuer_id`.
* - otherwise, if `offer_paths` is present (invoice_request for an offer without id):
* - MUST reject the invoice if `invoice_node_id` is not equal to the final `blinded_node_id` it sent the `invoice_request` to.
* - otherwise (invoice_request without an offer):
* - MAY reject the invoice if it cannot confirm that `invoice_node_id` is correct, out-of-band.
*
@ -159,7 +161,7 @@ static struct command_result *listinvreqs_done(struct command *cmd,
return fail_inv(cmd, inv, "invoice_request no longer available");
/* We only save ones without offers to the db! */
assert(!inv->inv->offer_issuer_id);
assert(!inv->inv->offer_issuer_id && !inv->inv->offer_paths);
/* BOLT-offers #12:
* - MUST reject the invoice if `signature` is not a valid signature

View file

@ -933,7 +933,7 @@ static struct command_result *listoffers_done(struct command *cmd,
* - if `offer_issuer_id` is present:
* - MUST set `invoice_node_id` to `offer_issuer_id`.
*/
/* We always provide an offer_issuer_id! */
/* FIXME: We always provide an offer_issuer_id! */
ir->inv->invoice_node_id = ir->inv->offer_issuer_id;
/* BOLT-offers #12:
@ -1048,16 +1048,16 @@ struct command_result *handle_invoice_request(struct command *cmd,
/* BOLT-offers #12:
*
* - otherwise (no `offer_issuer_id`, not a response to our offer):
* - otherwise (no `offer_issuer_id` or `offer_paths`, not a response to our offer):
*/
/* FIXME-OFFERS: handle this! */
if (!ir->invreq->offer_issuer_id) {
if (!ir->invreq->offer_issuer_id && !ir->invreq->offer_paths) {
return fail_invreq(cmd, ir, "Not based on an offer");
}
/* BOLT-offers #12:
*
* - if `offer_issuer_id` is present (response to an offer):
* - if `offer_issuer_id` or `offer_paths` are present (response to an offer):
* - MUST fail the request if the offer fields do not exactly match a
* valid, unexpired offer.
*/

View file

@ -551,8 +551,11 @@ struct command_result *json_offer(struct command *cmd,
}
/* BOLT-offers #12:
* - MUST set `offer_issuer_id` to the node's public key to request the
* invoice from.
* - if it includes `offer_paths`:
*...
* - otherwise:
* - MUST set `offer_issuer_id` to the node's public key to request the
* invoice from.
*/
offer->offer_issuer_id = tal_dup(offer, struct pubkey, &id);