diff --git a/common/bolt12.c b/common/bolt12.c index 07ef925db..e31b97f8c 100644 --- a/common/bolt12.c +++ b/common/bolt12.c @@ -17,17 +17,19 @@ bool bolt12_chains_match(const struct bitcoin_blkid *chains, { /* BOLT-offers #12: * - if the chain for the invoice is not solely bitcoin: - * - MUST specify `chains` the offer is valid for. + * - MUST specify `offer_chains` the offer is valid for. * - otherwise: - * - the bitcoin chain is implied as the first and only entry. + * - MAY omit `offer_chains`, implying that bitcoin is only chain. */ /* BOLT-offers #12: - * The reader of an invoice_request: + * A reader of an offer: *... - * - if `chain` is not present: - * - MUST fail the request if bitcoin is not a supported chain. - * - otherwise: - * - MUST fail the request if `chain` is not a supported chain. + * - if `offer_chains` is not set: + * - if the node does not accept bitcoin invoices: + * - MUST NOT respond to the offer + * - otherwise: (`offer_chains` is set): + * - if the node does not accept invoices for any of the `chains`: + * - MUST NOT respond to the offer */ if (!chains) { max_num_chains = 1; @@ -556,7 +558,9 @@ struct tlv_invoice *invoice_for_invreq(const tal_t *ctx, /* BOLT-offers #12: * A writer of an invoice: - * - MUST copy all non-signature fields from the invreq (including + *... + * - if the invoice is in response to an `invoice_request`: + * - MUST copy all non-signature fields from the `invoice_request` (including * unknown fields). */ len = tlv_span(wire, 0, 159, &start); diff --git a/common/bolt12.h b/common/bolt12.h index 879a09279..80b43bb57 100644 --- a/common/bolt12.h +++ b/common/bolt12.h @@ -10,12 +10,12 @@ struct feature_set; /* BOLT-offers #12: - * - if `relative_expiry` is present: + * - if `invoice_relative_expiry` is present: * - MUST reject the invoice if the current time since 1970-01-01 UTC - * is greater than `created_at` plus `seconds_from_creation`. + * is greater than `invoice_created_at` plus `seconds_from_creation`. * - otherwise: * - MUST reject the invoice if the current time since 1970-01-01 UTC - * is greater than `created_at` plus 7200. + * is greater than `invoice_created_at` plus 7200. */ #define BOLT12_DEFAULT_REL_EXPIRY 7200 diff --git a/common/bolt12_merkle.c b/common/bolt12_merkle.c index e08c8f66b..870e440ee 100644 --- a/common/bolt12_merkle.c +++ b/common/bolt12_merkle.c @@ -10,7 +10,8 @@ #endif /* BOLT-offers #12: - * TLV types 240 through 1000 are considered signature elements. + * Each form is signed using one or more *signature TLV elements*: TLV + * types 240 through 1000 (inclusive). */ static bool is_signature_field(const struct tlv_field *field) { @@ -193,18 +194,17 @@ void merkle_tlv(const struct tlv_field *fields, struct sha256 *merkle) /* BOLT-offers #12: * All signatures are created as per - * [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki), + * [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) * and tagged as recommended there. Thus we define H(`tag`,`msg`) as * SHA256(SHA256(`tag`) || SHA256(`tag`) || `msg`), and SIG(`tag`,`msg`,`key`) * as the signature of H(`tag`,`msg`) using `key`. * - * Each form is signed using one or more TLV signature elements; TLV - * types 240 through 1000 are considered signature elements. For these - * the tag is "lightning" || `messagename` || `fieldname`, and `msg` is the - * Merkle-root; "lightning" is the literal 9-byte ASCII string, - * `messagename` is the name of the TLV stream being signed (i.e. "offer", - * "invoice_request" or "invoice") and the `fieldname` is the TLV field - * containing the signature (e.g. "signature" or "refund_signature"). + * Each form is signed using one or more *signature TLV elements*: TLV types + * 240 through 1000 (inclusive). For these, the tag is "lightning" || + * `messagename` || `fieldname`, and `msg` is the Merkle-root; "lightning" is + * the literal 9-byte ASCII string, `messagename` is the name of the TLV + * stream being signed (i.e. "invoice_request" or "invoice") and the + * `fieldname` is the TLV field containing the signature (e.g. "signature"). */ void sighash_from_merkle(const char *messagename, const char *fieldname, diff --git a/common/iso4217.h b/common/iso4217.h index aca72b4b0..96f34221d 100644 --- a/common/iso4217.h +++ b/common/iso4217.h @@ -5,8 +5,8 @@ /* BOLT-offers #12: * - * - MUST specify `iso4217` as an ISO 4712 three-letter code. - * - MUST specify `amount` in the currency unit adjusted by the ISO 4712 + * - MUST specify `offer_currency` `iso4217` as an ISO 4712 three-letter code. + * - MUST specify `offer_amount` in the currency unit adjusted by the ISO 4712 * exponent (e.g. USD cents). */ struct iso4217_name_and_divisor { diff --git a/devtools/bolt12-cli.c b/devtools/bolt12-cli.c index 58ea2d21b..9d815cff8 100644 --- a/devtools/bolt12-cli.c +++ b/devtools/bolt12-cli.c @@ -362,12 +362,12 @@ static void print_relative_expiry(u64 *created_at, u32 *relative) return; /* BOLT-offers #12: - * - if `relative_expiry` is present: + * - if `invoice_relative_expiry` is present: * - MUST reject the invoice if the current time since 1970-01-01 UTC - * is greater than `created_at` plus `seconds_from_creation`. + * is greater than `invoice_created_at` plus `seconds_from_creation`. * - otherwise: * - MUST reject the invoice if the current time since 1970-01-01 UTC - * is greater than `created_at` plus 7200. + * is greater than `invoice_created_at` plus 7200. */ if (!relative) printf("invoice_relative_expiry: %u (%s) (default)\n", diff --git a/lightningd/offer.c b/lightningd/offer.c index 4f1975cfe..db3ee5b3b 100644 --- a/lightningd/offer.c +++ b/lightningd/offer.c @@ -463,8 +463,8 @@ static struct command_result *json_createinvoicerequest(struct command *cmd, } /* BOLT-offers #12: - * - MUST set `signature` `sig` as detailed in - * [Signature Calculation](#signature-calculation) using the `payer_key`. + * - MUST set `signature`.`sig` as detailed in + * [Signature Calculation](#signature-calculation) using the `invreq_payer_id`. */ /* This populates the ->fields from our entries */ invreq->fields = tlv_make_fields(invreq, tlv_invoice_request); diff --git a/plugins/fetchinvoice.c b/plugins/fetchinvoice.c index 7870268b6..a70577065 100644 --- a/plugins/fetchinvoice.c +++ b/plugins/fetchinvoice.c @@ -266,8 +266,8 @@ static struct command_result *handle_invreq_response(struct command *cmd, json_add_string(out, "invoice", invoice_encode(tmpctx, inv)); json_object_start(out, "changes"); /* BOLT-offers #12: - * - SHOULD confirm authorization if `msat` is not within the amount - * range authorized. + * - SHOULD confirm authorization if `invoice_amount`.`msat` is not within + * the amount range authorized. */ /* We always tell them this unless it's trivial to calc and * exactly as expected. */ @@ -436,17 +436,13 @@ static struct command_result *param_offer(struct command *cmd, fail)); /* BOLT-offers #12: * A reader of an offer: - * - if the offer contains any unknown TLV fields greater or equal to 80: + * - if the offer contains any TLV fields greater or equal to 80: * - MUST NOT respond to the offer. * - if `offer_features` contains unknown _odd_ bits that are non-zero: * - MUST ignore the bit. * - if `offer_features` contains unknown _even_ bits that are non-zero: * - MUST NOT respond to the offer. * - SHOULD indicate the unknown bit to the user. - * - if `offer_description` is not set: - * - MUST NOT respond to the offer. - * - if `offer_node_id` is not set: - * - MUST NOT respond to the offer. */ for (size_t i = 0; i < tal_count((*offer)->fields); i++) { if ((*offer)->fields[i].numtype > 80) { @@ -467,6 +463,13 @@ static struct command_result *param_offer(struct command *cmd, "unknown feature %i", badf)); } + + /* BOLT-offers #12: + * - if `offer_description` is not set: + * - MUST NOT respond to the offer. + * - if `offer_node_id` is not set: + * - MUST NOT respond to the offer. + */ if (!(*offer)->offer_description) return command_fail_badparam(cmd, name, buffer, tok, "Offer does not contain a description"); @@ -1012,7 +1015,7 @@ static struct command_result *json_fetchinvoice(struct command *cmd, /* BOLT-offers #12: * - SHOULD not respond to an offer if the current time is after - * `absolute_expiry`. + * `offer_absolute_expiry`. */ if (sent->offer->offer_absolute_expiry && time_now().ts.tv_sec > *sent->offer->offer_absolute_expiry) @@ -1053,7 +1056,7 @@ static struct command_result *json_fetchinvoice(struct command *cmd, /* BOLT-offers #12: * - if `offer_quantity_max` is present: - * - MUST set `invreq_quantity` + * - MUST set `invreq_quantity` to greater than zero. * - if `offer_quantity_max` is non-zero: * - MUST set `invreq_quantity` less than or equal to * `offer_quantity_max`. @@ -1062,6 +1065,9 @@ static struct command_result *json_fetchinvoice(struct command *cmd, if (!invreq->invreq_quantity) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "quantity parameter required"); + if (*invreq->invreq_quantity == 0) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "quantity parameter must be non-zero"); if (*invreq->offer_quantity_max && *invreq->invreq_quantity > *invreq->offer_quantity_max) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, @@ -1448,9 +1454,9 @@ static struct command_result *json_sendinvoice(struct command *cmd, return command_param_failed(); /* BOLT-offers #12: - * The writer: - * - MUST copy all non-signature fields from the invreq (including - * unknown fields). + * - if the invoice is in response to an `invoice_request`: + * - MUST copy all non-signature fields from the `invoice_request` + * (including unknown fields). */ sent->inv = invoice_for_invreq(sent, sent->invreq); diff --git a/plugins/offers.c b/plugins/offers.c index 59e7aeb8f..a34e0f07a 100644 --- a/plugins/offers.c +++ b/plugins/offers.c @@ -292,9 +292,8 @@ static bool json_add_blinded_paths(struct json_stream *js, json_array_end(js); /* BOLT-offers #12: - * - MUST reject the invoice if `blinded_payinfo` does not contain - * exactly as many `payinfo` as total `onionmsg_path` in - * `blinded_path`. + * - MUST reject the invoice if `invoice_blindedpay` does not contain + * exactly one `blinded_payinfo` per `invoice_paths`.`blinded_path`. */ if (blindedpay && n != tal_count(blindedpay)) { json_add_string(js, "warning_invalid_invoice_blindedpay", @@ -594,7 +593,7 @@ static bool json_add_fallbacks(struct json_stream *js, json_add_hex_talarr(js, "hex", fallbacks[i]->address); /* BOLT-offers #12: - * - for the bitcoin chain, if the invoice specifies `fallbacks`: + * - for the bitcoin chain, if the invoice specifies `invoice_fallbacks`: * - MUST ignore any `fallback_address` for which `version` is * greater than 16. * - MUST ignore any `fallback_address` for which `address` is @@ -749,14 +748,6 @@ static void json_add_b12_invoice(struct json_stream *js, * exactly one `blinded_payinfo` per `invoice_paths`.`blinded_path`. */ if (invoice->invoice_paths) { - /* BOLT-offers #12: - * - if `blinded_path` is present: - * - MUST reject the invoice if `blinded_payinfo` is not - * present. - * - MUST reject the invoice if `blinded_payinfo` does not - * contain exactly as many `payinfo` as total `onionmsg_path` - * in `blinded_path`. - */ if (!invoice->invoice_blindedpay) { json_add_string(js, "warning_missing_invoice_blindedpay", "invoices with paths without blindedpay are invalid"); diff --git a/plugins/offers_invreq_hook.c b/plugins/offers_invreq_hook.c index 26ff2b682..9d7989568 100644 --- a/plugins/offers_invreq_hook.c +++ b/plugins/offers_invreq_hook.c @@ -624,7 +624,13 @@ static struct command_result *check_previous_invoice(struct command *cmd, } /* BOLT-offers #12: - * - MUST fail the request if `signature` is not correct. + + * - MUST fail the request if `signature` is not correct as detailed in + * [Signature Calculation](#signature-calculation) using the + * `invreq_payer_id`. + *... + * - MUST reject the invoice if `signature` is not a valid signature using + * `invoice_node_id` as described in [Signature Calculation](#signature-calculation). */ static bool check_payer_sig(struct command *cmd, const struct tlv_invoice_request *invreq, @@ -645,12 +651,12 @@ static struct command_result *invreq_amount_by_quantity(struct command *cmd, assert(ir->invreq->offer_amount); /* BOLT-offers #12: - * - MUST calculate the *base invoice amount* using the offer `amount`: + * - MUST calculate the *expected amount* using the `offer_amount`: */ *raw_amt = *ir->invreq->offer_amount; /* BOLT-offers #12: - * - if request contains `quantity`, multiply by `quantity`. + * - if `invreq_quantity` is present, multiply by `invreq_quantity`.`quantity`. */ if (ir->invreq->invreq_quantity) { if (mul_overflows_u64(*ir->invreq->invreq_quantity, *raw_amt)) { @@ -703,11 +709,9 @@ static struct command_result *handle_amount_and_recurrence(struct command *cmd, struct amount_msat base_inv_amount) { /* BOLT-offers #12: - * - if the offer included `amount`: - *... - * - if the request contains `amount`: - * - MUST fail the request if its `amount` is less than the - * *base invoice amount*. + * - if `invreq_amount` is present: + * - MUST fail the request if `invreq_amount`.`msat` is less than the + * *expected amount*. */ if (ir->invreq->offer_amount && ir->invreq->invreq_amount) { if (amount_msat_less(amount_msat(*ir->invreq->invreq_amount), base_inv_amount)) { @@ -716,8 +720,8 @@ static struct command_result *handle_amount_and_recurrence(struct command *cmd, &base_inv_amount)); } /* BOLT-offers #12: - * - MAY fail the request if its `amount` is much greater than - * the *base invoice amount*. + * - MAY fail the request if `invreq_amount`.`msat` greatly exceeds + * the *expected amount*. */ /* Much == 5? Easier to divide and compare, than multiply. */ if (amount_msat_greater(amount_msat_div(amount_msat(*ir->invreq->invreq_amount), 5), @@ -726,13 +730,15 @@ static struct command_result *handle_amount_and_recurrence(struct command *cmd, type_to_string(tmpctx, struct amount_msat, &base_inv_amount)); } - /* BOLT-offers #12: - * - MUST use the request's `amount` as the *base invoice - * amount*. - */ base_inv_amount = amount_msat(*ir->invreq->invreq_amount); } + /* BOLT-offers #12: + * - if `invreq_amount` is present: + * - MUST set `invoice_amount` to `invreq_amount` + * - otherwise: + * - MUST set `invoice_amount` to the *expected amount*. + */ /* This may be adjusted by recurrence if proportional_amount set */ ir->inv->invoice_amount = tal_dup(ir->inv, u64, &base_inv_amount.millisatoshis); /* Raw: wire protocol */ @@ -794,10 +800,9 @@ static struct command_result *convert_currency(struct command *cmd, return err; /* BOLT-offers #12: - * - MUST calculate the *base invoice amount* using the offer - * `amount`: - * - if offer `currency` is not the invoice currency, convert - * to the invoice currency. + * - MUST calculate the *expected amount* using the `offer_amount`: + * - if `offer_currency` is not the `invreq_chain` currency, convert to the + * `invreq_chain` currency. */ iso4217 = find_iso4217(ir->invreq->offer_currency, tal_bytelen(ir->invreq->offer_currency)); @@ -900,7 +905,7 @@ static struct command_result *listoffers_done(struct command *cmd, } /* BOLT-offers #12: - * - MUST fail the request if `invreq_signature` is not correct as + * - MUST fail the request if `signature` is not correct as * detailed in [Signature Calculation](#signature-calculation) using * the `invreq_payer_id`. */ @@ -940,10 +945,10 @@ static struct command_result *listoffers_done(struct command *cmd, } /* BOLT-offers #12: - * The writer of an invoice: + * A writer of an invoice: *... * - if the invoice is in response to an `invoice_request`: - * - MUST copy all non-signature fields from the invreq (including + * - MUST copy all non-signature fields from the `invoice_request` (including * unknown fields). */ ir->inv = invoice_for_invreq(cmd, ir->invreq); @@ -1035,7 +1040,7 @@ struct command_result *handle_invoice_request(struct command *cmd, /* BOLT-offers #12: * - * The reader of an invreq: + * The reader: *... * - if `invreq_features` contains unknown _even_ bits that are non-zero: * - MUST fail the request. diff --git a/plugins/offers_offer.c b/plugins/offers_offer.c index 648bc7d52..e2ba3d81d 100644 --- a/plugins/offers_offer.c +++ b/plugins/offers_offer.c @@ -44,8 +44,8 @@ static struct command_result *param_amount(struct command *cmd, /* BOLT-offers #12: * - * - MUST specify `iso4217` as an ISO 4712 three-letter code. - * - MUST specify `amount` in the currency unit adjusted by the ISO 4712 + * - MUST specify `offer_currency` `iso4217` as an ISO 4712 three-letter code. + * - MUST specify `offer_amount` in the currency unit adjusted by the ISO 4712 * exponent (e.g. USD cents). */ if (tok->end - tok->start < ISO4217_NAMELEN)