From 1e3cb015469088880e7976647fd5625598434c5e Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 9 Nov 2022 13:02:00 +1030 Subject: [PATCH] bolt12: import the latest spec, update to fit. I know this is an unforgivably large diff, but the spec has changed so much that most of this amounts to a rewrite. Some points: * We no longer have "offer_id" fields, we generate that locally, as all offer fields are mirrored into invoice_request and then invoice. * Because of that mirroring, field names all have explicit offer/invreq/invoice prefixes. * The `refund_for` fields have been removed from spec: will re-add locally later. * quantity_min was removed, max == 0 now mean "must specify a quantity". * I have put recurrence fields back in locally. This brings us to 655df03d8729c0918bdacac99eb13fdb0ee93345 ("BOLT 12: add explicit invoice_node_id.") Signed-off-by: Rusty Russell --- common/bolt12.c | 54 +-- common/bolt12.h | 20 +- common/test/run-bolt12_period.c | 2 +- devtools/bolt12-cli.c | 260 +++++------ doc/lightning-decode.7.md | 10 +- doc/lightning-offer.7.md | 11 +- doc/schemas/decode.schema.json | 23 +- lightningd/invoice.c | 97 +++-- lightningd/offer.c | 100 ++--- lightningd/pay.c | 4 +- lightningd/test/run-invoice-select-inchan.c | 3 + plugins/bkpr/bookkeeper.c | 6 +- plugins/fetchinvoice.c | 452 +++++++++----------- plugins/offers.c | 380 ++++++++-------- plugins/offers_invreq_hook.c | 428 +++++++++--------- plugins/offers_offer.c | 110 +++-- plugins/pay.c | 94 ++-- tests/test_pay.py | 32 +- wire/bolt12_exp_wire.csv | 268 +++++++----- wire/bolt12_wire.csv | 268 +++++++----- wire/extracted_bolt12_01_recurrence.patch | 134 ++++-- 21 files changed, 1377 insertions(+), 1379 deletions(-) diff --git a/common/bolt12.c b/common/bolt12.c index 192ecdaec3bf..30629c90d741 100644 --- a/common/bolt12.c +++ b/common/bolt12.c @@ -11,9 +11,9 @@ #include /* If chains is NULL, max_num_chains is ignored */ -static bool bolt12_chains_match(const struct bitcoin_blkid *chains, - size_t max_num_chains, - const struct chainparams *must_be_chain) +bool bolt12_chains_match(const struct bitcoin_blkid *chains, + size_t max_num_chains, + const struct chainparams *must_be_chain) { /* BOLT-offers #12: * - if the chain for the invoice is not solely bitcoin: @@ -181,26 +181,13 @@ struct tlv_offer *offer_decode(const tal_t *ctx, *fail = check_features_and_chain(ctx, our_features, must_be_chain, - offer->features, + offer->offer_features, BOLT12_OFFER_FEATURE, - offer->chains, - tal_count(offer->chains)); + offer->offer_chains, + tal_count(offer->offer_chains)); if (*fail) return tal_free(offer); - /* BOLT-offers #12: - * - if `signature` is present, but is not a valid signature using - * `node_id` as described in [Signature Calculation](#signature-calculation): - * - MUST NOT respond to the offer. - */ - if (offer->signature) { - *fail = check_signature(ctx, offer->fields, - "offer", "signature", - offer->node_id, offer->signature); - if (*fail) - return tal_free(offer); - } - return offer; } @@ -230,15 +217,15 @@ struct tlv_invoice_request *invrequest_decode(const tal_t *ctx, invrequest = fromwire_tlv_invoice_request(ctx, &data, &dlen); if (!invrequest) { - *fail = tal_fmt(ctx, "invalid invoice_request data"); + *fail = tal_fmt(ctx, "invalid invreq data"); return NULL; } *fail = check_features_and_chain(ctx, our_features, must_be_chain, - invrequest->features, + invrequest->invreq_features, BOLT12_INVREQ_FEATURE, - invrequest->chain, 1); + invrequest->invreq_chain, 1); if (*fail) return tal_free(invrequest); @@ -277,9 +264,9 @@ struct tlv_invoice *invoice_decode_nosig(const tal_t *ctx, *fail = check_features_and_chain(ctx, our_features, must_be_chain, - invoice->features, + invoice->invoice_features, BOLT12_INVOICE_FEATURE, - invoice->chain, 1); + invoice->invreq_chain, 1); if (*fail) return tal_free(invoice); @@ -328,7 +315,7 @@ static u64 time_change(u64 prevstart, u32 number, } u64 offer_period_start(u64 basetime, size_t n, - const struct tlv_offer_recurrence *recur) + const struct recurrence *recur) { /* BOLT-offers-recurrence #12: * 1. A `time_unit` defining 0 (seconds), 1 (days), 2 (months), @@ -349,9 +336,9 @@ u64 offer_period_start(u64 basetime, size_t n, } } -void offer_period_paywindow(const struct tlv_offer_recurrence *recurrence, - const struct tlv_offer_recurrence_paywindow *recurrence_paywindow, - const struct tlv_offer_recurrence_base *recurrence_base, +void offer_period_paywindow(const struct recurrence *recurrence, + const struct recurrence_paywindow *recurrence_paywindow, + const struct recurrence_base *recurrence_base, u64 basetime, u64 period_idx, u64 *start, u64 *end) { @@ -364,9 +351,9 @@ void offer_period_paywindow(const struct tlv_offer_recurrence *recurrence, /* BOLT-offers-recurrence #12: * - if the offer has a `recurrence_basetime` or the * `recurrence_counter` is non-zero: - * - SHOULD NOT send an `invoice_request` for a period prior to + * - SHOULD NOT send an `invreq` for a period prior to * `seconds_before` seconds before that period start. - * - SHOULD NOT send an `invoice_request` for a period later + * - SHOULD NOT send an `invreq` for a period later * than `seconds_after` seconds past that period start. */ *start = pstart - recurrence_paywindow->seconds_before; @@ -381,7 +368,7 @@ void offer_period_paywindow(const struct tlv_offer_recurrence *recurrence, } else { /* BOLT-offers-recurrence #12: * - otherwise: - * - SHOULD NOT send an `invoice_request` with + * - SHOULD NOT send an `invreq` with * `recurrence_counter` is non-zero for a period whose * immediate predecessor has not yet begun. */ @@ -392,7 +379,7 @@ void offer_period_paywindow(const struct tlv_offer_recurrence *recurrence, recurrence); /* BOLT-offers-recurrence #12: - * - SHOULD NOT send an `invoice_request` for a period which + * - SHOULD NOT send an `invreq` for a period which * has already passed. */ *end = offer_period_start(basetime, period_idx+1, @@ -413,7 +400,8 @@ struct tlv_invoice *invoice_decode(const tal_t *ctx, if (invoice) { *fail = check_signature(ctx, invoice->fields, "invoice", "signature", - invoice->node_id, invoice->signature); + invoice->invoice_node_id, + invoice->signature); if (*fail) invoice = tal_free(invoice); } diff --git a/common/bolt12.h b/common/bolt12.h index 6a732644bbd8..cce31a1b6876 100644 --- a/common/bolt12.h +++ b/common/bolt12.h @@ -49,13 +49,13 @@ char *invrequest_encode(const tal_t *ctx, /** * invrequest_decode - decode this complete bolt12 text into a TLV. * @ctx: the context to allocate return or *@fail off. - * @b12: the invoice_request string - * @b12len: the invoice_request string length + * @b12: the invreq string + * @b12len: the invreq string length * @our_features: if non-NULL, feature set to check against. * @must_be_chain: if non-NULL, chain to enforce. * @fail: pointer to descriptive error string, set if this returns NULL. * - * Note: invoice_request doesn't always have a signature, so no checking is done! + * Note: invreq doesn't always have a signature, so no checking is done! */ struct tlv_invoice_request *invrequest_decode(const tal_t *ctx, const char *b12, size_t b12len, @@ -103,14 +103,20 @@ bool bolt12_check_signature(const struct tlv_field *fields, bool bolt12_chain_matches(const struct bitcoin_blkid *chain, const struct chainparams *must_be_chain); +/* Given an array of max_num_chains chains (or NULL == bitcoin), does + * it match? */ +bool bolt12_chains_match(const struct bitcoin_blkid *chains, + size_t max_num_chains, + const struct chainparams *must_be_chain); + /* Given a basetime, when does period N start? */ u64 offer_period_start(u64 basetime, size_t n, - const struct tlv_offer_recurrence *recurrence); + const struct recurrence *recurrence); /* Get the start and end of the payment window for period N. */ -void offer_period_paywindow(const struct tlv_offer_recurrence *recurrence, - const struct tlv_offer_recurrence_paywindow *recurrence_paywindow, - const struct tlv_offer_recurrence_base *recurrence_base, +void offer_period_paywindow(const struct recurrence *recurrence, + const struct recurrence_paywindow *recurrence_paywindow, + const struct recurrence_base *recurrence_base, u64 basetime, u64 period_idx, u64 *period_start, u64 *period_end); diff --git a/common/test/run-bolt12_period.c b/common/test/run-bolt12_period.c index 9c29e154a449..8b44328bda10 100644 --- a/common/test/run-bolt12_period.c +++ b/common/test/run-bolt12_period.c @@ -192,7 +192,7 @@ int main(int argc, char *argv[]) /* We deal in UTC; mktime() uses local time */ setenv("TZ", "", 1); json_for_each_arr(i, t, toks) { - struct tlv_offer_recurrence recurrence; + struct recurrence recurrence; int unit; const jsmntok_t *exp; u64 base, secs, n; diff --git a/devtools/bolt12-cli.c b/devtools/bolt12-cli.c index b2fdd00c2b5d..0017995cdfc8 100644 --- a/devtools/bolt12-cli.c +++ b/devtools/bolt12-cli.c @@ -157,20 +157,15 @@ static void print_node_id(const struct pubkey *node_id) printf("node_id: %s\n", type_to_string(tmpctx, struct pubkey, node_id)); } -static void print_quantity_min(u64 min) -{ - printf("quantity_min: %"PRIu64"\n", min); -} - static void print_quantity_max(u64 max) { printf("quantity_max: %"PRIu64"\n", max); } -static bool print_recurrance(const struct tlv_offer_recurrence *recurrence, - const struct tlv_offer_recurrence_paywindow *paywindow, +static bool print_recurrance(const struct recurrence *recurrence, + const struct recurrence_paywindow *paywindow, const u32 *limit, - const struct tlv_offer_recurrence_base *base) + const struct recurrence_base *base) { const char *unit; bool ok = true; @@ -293,17 +288,6 @@ static bool print_blindedpaths(struct blinded_path **paths, return true; } -static void print_send_invoice(void) -{ - printf("send_invoice\n"); -} - -static void print_refund_for(const struct sha256 *payment_hash) -{ - printf("refund_for: %s\n", - type_to_string(tmpctx, struct sha256, payment_hash)); -} - static bool print_signature(const char *messagename, const char *fieldname, const struct tlv_field *fields, @@ -328,12 +312,6 @@ static bool print_signature(const char *messagename, return true; } -static void print_offer_id(const struct sha256 *offer_id) -{ - printf("offer_id: %s\n", - type_to_string(tmpctx, struct sha256, offer_id)); -} - static void print_quantity(u64 q) { printf("quantity: %"PRIu64"\n", q); @@ -363,13 +341,14 @@ static bool print_recurrence_counter_with_base(const u32 *recurrence_counter, return true; } -static void print_payer_key(const struct pubkey *payer_key, - const u8 *payer_info) +static void print_payer_id(const struct pubkey *payer_id, + const u8 *metadata) { - printf("payer_key: %s", - type_to_string(tmpctx, struct pubkey, payer_key)); - if (payer_info) - printf(" (payer_info %s)", tal_hex(tmpctx, payer_info)); + printf("invreq_payer_id: %s", + type_to_string(tmpctx, struct pubkey, payer_id)); + if (metadata) + printf(" (invreq_metadata %s)", + tal_hex(tmpctx, metadata)); printf("\n"); } @@ -391,11 +370,6 @@ static void print_payment_hash(const struct sha256 *payment_hash) type_to_string(tmpctx, struct sha256, payment_hash)); } -static void print_cltv(u32 cltv) -{ - printf("min_final_cltv_expiry: %u\n", cltv); -} - static void print_relative_expiry(u64 *created_at, u32 *relative) { /* Ignore if already malformed */ @@ -553,42 +527,31 @@ int main(int argc, char *argv[]) if (!offer) errx(ERROR_BAD_DECODE, "Bad offer: %s", fail); - if (offer->send_invoice) - print_send_invoice(); - if (offer->chains) - print_chains(offer->chains); - if (offer->refund_for) - print_refund_for(offer->refund_for); - if (offer->amount) - well_formed &= print_amount(offer->chains, - offer->currency, - *offer->amount); - if (must_have(offer, description)) - print_description(offer->description); - if (offer->issuer) - print_issuer(offer->issuer); - if (must_have(offer, node_id)) - print_node_id(offer->node_id); - if (offer->quantity_min) - print_quantity_min(*offer->quantity_min); - if (offer->quantity_max) - print_quantity_max(*offer->quantity_max); - if (offer->recurrence) - well_formed &= print_recurrance(offer->recurrence, - offer->recurrence_paywindow, - offer->recurrence_limit, - offer->recurrence_base); - if (offer->absolute_expiry) - print_absolute_expiry(*offer->absolute_expiry); - if (offer->features) - print_features(offer->features); - if (offer->paths) - print_blindedpaths(offer->paths, NULL); - if (offer->signature && offer->node_id) - well_formed &= print_signature("offer", "signature", - offer->fields, - offer->node_id, - offer->signature); + if (offer->offer_chains) + print_chains(offer->offer_chains); + if (offer->offer_amount) + well_formed &= print_amount(offer->offer_chains, + offer->offer_currency, + *offer->offer_amount); + if (must_have(offer, offer_description)) + print_description(offer->offer_description); + if (offer->offer_issuer) + print_issuer(offer->offer_issuer); + if (must_have(offer, offer_node_id)) + print_node_id(offer->offer_node_id); + if (offer->offer_quantity_max) + print_quantity_max(*offer->offer_quantity_max); + if (offer->offer_recurrence) + well_formed &= print_recurrance(offer->offer_recurrence, + offer->offer_recurrence_paywindow, + offer->offer_recurrence_limit, + offer->offer_recurrence_base); + if (offer->offer_absolute_expiry) + print_absolute_expiry(*offer->offer_absolute_expiry); + if (offer->offer_features) + print_features(offer->offer_features); + if (offer->offer_paths) + print_blindedpaths(offer->offer_paths, NULL); if (!print_extra_fields(offer->fields)) well_formed = false; } else if (streq(hrp, "lnr")) { @@ -596,43 +559,35 @@ int main(int argc, char *argv[]) = invrequest_decode(ctx, argv[2], strlen(argv[2]), NULL, NULL, &fail); if (!invreq) - errx(ERROR_BAD_DECODE, "Bad invoice_request: %s", fail); - - if (invreq->chain) - print_chain(invreq->chain); - if (must_have(invreq, payer_key)) - print_payer_key(invreq->payer_key, invreq->payer_info); - if (invreq->payer_note) - print_payer_note(invreq->payer_note); - if (must_have(invreq, offer_id)) - print_offer_id(invreq->offer_id); - if (must_have(invreq, amount)) - well_formed &= print_amount(invreq->chain, + errx(ERROR_BAD_DECODE, "Bad invreq: %s", fail); + + if (invreq->invreq_chain) + print_chain(invreq->invreq_chain); + if (must_have(invreq, invreq_payer_id)) + print_payer_id(invreq->invreq_payer_id, + invreq->invreq_metadata); + if (invreq->invreq_payer_note) + print_payer_note(invreq->invreq_payer_note); + if (must_have(invreq, invreq_amount)) + well_formed &= print_amount(invreq->invreq_chain, NULL, - *invreq->amount); - if (invreq->features) - print_features(invreq->features); - if (invreq->quantity) - print_quantity(*invreq->quantity); + *invreq->invreq_amount); + if (invreq->invreq_features) + print_features(invreq->invreq_features); + if (invreq->invreq_quantity) + print_quantity(*invreq->invreq_quantity); if (must_have(invreq, signature)) { - if (!print_signature("invoice_request", - "signature", - invreq->fields, - invreq->payer_key, - invreq->signature)) { - /* FIXME: We temporarily allow the old "payer_signature" name */ - well_formed &= print_signature("invoice_request", - "payer_signature", - invreq->fields, - invreq->payer_key, - invreq->signature); - } + well_formed = print_signature("invoice_request", + "signature", + invreq->fields, + invreq->invreq_payer_id, + invreq->signature); } - if (invreq->recurrence_counter) { - print_recurrence_counter(invreq->recurrence_counter, - invreq->recurrence_start); + if (invreq->invreq_recurrence_counter) { + print_recurrence_counter(invreq->invreq_recurrence_counter, + invreq->invreq_recurrence_start); } else { - must_not_have(invreq, recurrence_start); + must_not_have(invreq, invreq_recurrence_start); } if (!print_extra_fields(invreq->fields)) well_formed = false; @@ -643,70 +598,55 @@ int main(int argc, char *argv[]) if (!invoice) errx(ERROR_BAD_DECODE, "Bad invoice: %s", fail); - if (invoice->chain) - print_chain(invoice->chain); + if (invoice->invreq_chain) + print_chain(invoice->invreq_chain); - if (invoice->offer_id) { - print_offer_id(invoice->offer_id); - } - if (must_have(invoice, amount)) - well_formed &= print_amount(invoice->chain, + if (must_have(invoice, invoice_amount)) + well_formed &= print_amount(invoice->invreq_chain, NULL, - *invoice->amount); - if (must_have(invoice, description)) - print_description(invoice->description); - if (invoice->features) - print_features(invoice->features); - if (invoice->paths) { - must_have(invoice, blindedpay); - well_formed &= print_blindedpaths(invoice->paths, - invoice->blindedpay); + *invoice->invoice_amount); + if (must_have(invoice, offer_description)) + print_description(invoice->offer_description); + if (invoice->invoice_features) + print_features(invoice->invoice_features); + if (invoice->invoice_paths) { + must_have(invoice, invoice_blindedpay); + well_formed &= print_blindedpaths(invoice->invoice_paths, + invoice->invoice_blindedpay); } else - must_not_have(invoice, blindedpay); - if (invoice->issuer) - print_issuer(invoice->issuer); - if (must_have(invoice, node_id)) - print_node_id(invoice->node_id); - if (invoice->quantity) - print_quantity(*invoice->quantity); - if (invoice->refund_for) { - print_refund_for(invoice->refund_for); - if (must_have(invoice, refund_signature)) - well_formed &= print_signature("invoice", - "refund_signature", - invoice->fields, - invoice->payer_key, - invoice->refund_signature); - } else { - must_not_have(invoice, refund_signature); - } - if (invoice->recurrence_counter) { + must_not_have(invoice, invoice_blindedpay); + if (invoice->offer_issuer) + print_issuer(invoice->offer_issuer); + if (must_have(invoice, offer_node_id)) + print_node_id(invoice->offer_node_id); + if (invoice->invreq_quantity) + print_quantity(*invoice->invreq_quantity); + if (invoice->invreq_recurrence_counter) { well_formed &= - print_recurrence_counter_with_base(invoice->recurrence_counter, - invoice->recurrence_start, - invoice->recurrence_basetime); + print_recurrence_counter_with_base(invoice->invreq_recurrence_counter, + invoice->invreq_recurrence_start, + invoice->invoice_recurrence_basetime); } else { - must_not_have(invoice, recurrence_start); - must_not_have(invoice, recurrence_basetime); + must_not_have(invoice, invreq_recurrence_start); + must_not_have(invoice, invoice_recurrence_basetime); } - if (must_have(invoice, payer_key)) - print_payer_key(invoice->payer_key, invoice->payer_info); - if (must_have(invoice, created_at)) - print_created_at(*invoice->created_at); - if (invoice->payer_note) - print_payer_note(invoice->payer_note); - print_relative_expiry(invoice->created_at, - invoice->relative_expiry); - if (must_have(invoice, payment_hash)) - print_payment_hash(invoice->payment_hash); - if (must_have(invoice, cltv)) - print_cltv(*invoice->cltv); - if (invoice->fallbacks) - print_fallbacks(invoice->fallbacks); + if (must_have(invoice, invreq_payer_id)) + print_payer_id(invoice->invreq_payer_id, + invoice->invreq_metadata); + if (must_have(invoice, invoice_created_at)) + print_created_at(*invoice->invoice_created_at); + if (invoice->invreq_payer_note) + print_payer_note(invoice->invreq_payer_note); + print_relative_expiry(invoice->invoice_created_at, + invoice->invoice_relative_expiry); + if (must_have(invoice, invoice_payment_hash)) + print_payment_hash(invoice->invoice_payment_hash); + if (invoice->invoice_fallbacks) + print_fallbacks(invoice->invoice_fallbacks); if (must_have(invoice, signature)) well_formed &= print_signature("invoice", "signature", invoice->fields, - invoice->node_id, + invoice->offer_node_id, invoice->signature); if (!print_extra_fields(invoice->fields)) well_formed = false; diff --git a/doc/lightning-decode.7.md b/doc/lightning-decode.7.md index 83e58b26fd0d..375327ca2c99 100644 --- a/doc/lightning-decode.7.md +++ b/doc/lightning-decode.7.md @@ -50,8 +50,7 @@ If **type** is "bolt12 offer", and **valid** is *true*: - **path** (array of objects): an individual path: - **blinded\_node\_id** (pubkey): node_id of the hop - **encrypted\_recipient\_data** (hex): encrypted TLV entry for this hop - - **quantity\_min** (u64, optional): the minimum quantity - - **quantity\_max** (u64, optional): the maximum quantity + - **quantity\_max** (u64, optional): the maximum quantity (or, if 0, means any quantity) - **recurrence** (object, optional): how often to this offer should be used: - **time\_unit** (u32): the BOLT12 time unit - **period** (u32): how many *time_unit* per payment period @@ -80,7 +79,6 @@ If **type** is "bolt12 invoice", and **valid** is *true*: - **created\_at** (u64): the UNIX timestamp of invoice creation - **payment\_hash** (hex): the hash of the *payment_preimage* (always 64 characters) - **relative\_expiry** (u32): the number of seconds after *created_at* when this expires - - **min\_final\_cltv\_expiry** (u32): the number of blocks required by destination - **offer\_id** (hex, optional): the id of this offer (merkle hash of non-signature fields) (always 64 characters) - **chain** (hex, optional): which blockchain this invoice is for (missing implies bitcoin mainnet only) (always 64 characters) - **send\_invoice** (boolean, optional): present if this offer was a send_invoice offer (always *true*) @@ -102,7 +100,7 @@ If **type** is "bolt12 invoice", and **valid** is *true*: - **recurrence\_start** (u32, optional): the optional start period for a recurring payment - **recurrence\_basetime** (u32, optional): the UNIX timestamp of the first recurrence period start - **payer\_key** (pubkey, optional): the transient key which identifies the payer - - **payer\_info** (hex, optional): the payer-provided blob to derive payer_key + - **invreq\_metadata** (hex, optional): the payer-provided blob to derive payer_key - **fallbacks** (array of objects, optional): onchain addresses: - **version** (u8): Segwit address version - **hex** (hex): Raw encoded segwit address @@ -136,7 +134,7 @@ If **type** is "bolt12 invoice_request", and **valid** is *true*: - **quantity** (u64, optional): the quantity ordered - **recurrence\_counter** (u32, optional): the 0-based counter for a recurring payment - **recurrence\_start** (u32, optional): the optional start period for a recurring payment - - **payer\_info** (hex, optional): the payer-provided blob to derive payer_key + - **invreq\_metadata** (hex, optional): the payer-provided blob to derive payer_key - **recurrence\_signature** (bip340sig, optional): the payer key signature If **type** is "bolt12 invoice_request", and **valid** is *false*: @@ -217,4 +215,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:bbe57fd87e729e1203055d983a72757b9647ea67dca23c254a05b38b7b7020d9) +[comment]: # ( SHA256STAMP:3f0c78dd665bff6749352801b0fdd958ee138fda6ede5b3631d9cb136fc45d76) diff --git a/doc/lightning-offer.7.md b/doc/lightning-offer.7.md index b9ebd5736ade..541fb918c063 100644 --- a/doc/lightning-offer.7.md +++ b/doc/lightning-offer.7.md @@ -6,7 +6,7 @@ SYNOPSIS **(WARNING: experimental-offers only)** -**offer** *amount* *description* [*issuer*] [*label*] [*quantity_min*] [*quantity_max*] [*absolute_expiry*] [*recurrence*] [*recurrence_base*] [*recurrence_paywindow*] [*recurrence_limit*] [*single_use*] +**offer** *amount* *description* [*issuer*] [*label*] [*quantity_max*] [*absolute_expiry*] [*recurrence*] [*recurrence_base*] [*recurrence_paywindow*] [*recurrence_limit*] [*single_use*] DESCRIPTION ----------- @@ -43,10 +43,11 @@ reflects who is issuing this offer (i.e. you) if appropriate. The *label* field is an internal-use name for the offer, which can be any UTF-8 string. -The present of *quantity_min* or *quantity_max* indicates that the -invoice can specify more than one of the items within this (inclusive) -range. The *amount* for the invoice will need to be multiplied -accordingly. These are encoded in the offer. +The presence of *quantity_max* indicates that the +invoice can specify more than one of the items up (and including) +this maximum: 0 is a special value meaning "no maximuim". +The *amount* for the invoice will need to be multiplied +accordingly. This is encoded in the offer. The *absolute_expiry* is optionally the time the offer is valid until, in seconds since the first day of 1970 UTC. If not set, the offer diff --git a/doc/schemas/decode.schema.json b/doc/schemas/decode.schema.json index a48dcabdf1fc..d7b9e380937c 100644 --- a/doc/schemas/decode.schema.json +++ b/doc/schemas/decode.schema.json @@ -170,13 +170,9 @@ } } }, - "quantity_min": { - "type": "u64", - "description": "the minimum quantity" - }, "quantity_max": { "type": "u64", - "description": "the maximum quantity" + "description": "the maximum quantity (or, if 0, means any quantity)" }, "recurrence": { "type": "object", @@ -281,7 +277,6 @@ "features": {}, "absolute_expiry": {}, "paths": {}, - "quantity_min": {}, "quantity_max": {}, "recurrence": {}, "warning_offer_missing_description": { @@ -316,8 +311,7 @@ "description", "created_at", "payment_hash", - "relative_expiry", - "min_final_cltv_expiry" + "relative_expiry" ], "additionalProperties": false, "properties": { @@ -453,7 +447,7 @@ "type": "pubkey", "description": "the transient key which identifies the payer" }, - "payer_info": { + "invreq_metadata": { "type": "hex", "description": "the payer-provided blob to derive payer_key" }, @@ -474,10 +468,6 @@ "type": "u32", "description": "the number of seconds after *created_at* when this expires" }, - "min_final_cltv_expiry": { - "type": "u32", - "description": "the number of blocks required by destination" - }, "fallbacks": { "type": "array", "description": "onchain addresses", @@ -550,12 +540,11 @@ "recurrence_start": {}, "recurrence_basetime": {}, "payer_key": {}, - "payer_info": {}, + "invreq_metadata": {}, "timestamp": {}, "created_at": {}, "payment_hash": {}, "relative_expiry": {}, - "min_final_cltv_expiry": {}, "fallbacks": { "type": "array", "items": { @@ -681,7 +670,7 @@ "type": "pubkey", "description": "the transient key which identifies the payer" }, - "payer_info": { + "invreq_metadata": { "type": "hex", "description": "the payer-provided blob to derive payer_key" }, @@ -723,7 +712,7 @@ "recurrence_counter": {}, "recurrence_start": {}, "payer_key": {}, - "payer_info": {}, + "invreq_metadata": {}, "recurrence_signature": {}, "warning_invoice_request_missing_offer_id": { "type": "string", diff --git a/lightningd/invoice.c b/lightningd/invoice.c index b60dc01f8181..bd546c79e23a 100644 --- a/lightningd/invoice.c +++ b/lightningd/invoice.c @@ -70,10 +70,11 @@ static void json_add_invoice_fields(struct json_stream *response, tinv = invoice_decode(tmpctx, inv->invstring, strlen(inv->invstring), NULL, NULL, &fail); - if (tinv && tinv->payer_note) + /* FIXME-OFFERS: Rename all fields to offer_ as per spec */ + if (tinv && tinv->invreq_payer_note) json_add_stringn(response, "payer_note", - tinv->payer_note, - tal_bytelen(tinv->payer_note)); + tinv->invreq_payer_note, + tal_bytelen(tinv->invreq_payer_note)); } } @@ -1352,11 +1353,11 @@ static struct command_result *json_listinvoices(struct command *cmd, strlen(invstring), cmd->ld->our_features, NULL, &fail); - if (!b12 || !b12->payment_hash) { + if (!b12 || !b12->invoice_payment_hash) { return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Invalid invstring"); } - payment_hash = b12->payment_hash; + payment_hash = b12->invoice_payment_hash; } } @@ -1658,7 +1659,7 @@ static struct tlv_invoice *add_stub_blindedpath(const tal_t *ctx, tlv = tlv_encrypted_data_tlv_new(tmpctx); tlv->path_id = invoice_path_id(inv, &ld->invoicesecret_base, - inv->payment_hash); + inv->invoice_payment_hash); path->path[0]->encrypted_recipient_data = encrypt_tlv_encrypted_data(path->path[0], @@ -1668,22 +1669,23 @@ static struct tlv_invoice *add_stub_blindedpath(const tal_t *ctx, NULL, &path->path[0]->blinded_node_id); - inv->paths = tal_arr(inv, struct blinded_path *, 1); - inv->paths[0] = tal_steal(inv->paths, path); + inv->invoice_paths = tal_arr(inv, struct blinded_path *, 1); + inv->invoice_paths[0] = tal_steal(inv->invoice_paths, path); /* 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. */ - inv->blindedpay = tal_arr(inv, struct blinded_payinfo *, 1); - inv->blindedpay[0] = tal(inv->blindedpay, struct blinded_payinfo); - inv->blindedpay[0]->fee_base_msat = 0; - inv->blindedpay[0]->fee_proportional_millionths = 0; - inv->blindedpay[0]->cltv_expiry_delta = ld->config.cltv_final; - inv->blindedpay[0]->htlc_minimum_msat = AMOUNT_MSAT(0); - inv->blindedpay[0]->htlc_maximum_msat = AMOUNT_MSAT(21000000 * MSAT_PER_BTC); - inv->blindedpay[0]->features = NULL; + inv->invoice_blindedpay = tal_arr(inv, struct blinded_payinfo *, 1); + inv->invoice_blindedpay[0] = tal(inv->invoice_blindedpay, + struct blinded_payinfo); + inv->invoice_blindedpay[0]->fee_base_msat = 0; + inv->invoice_blindedpay[0]->fee_proportional_millionths = 0; + inv->invoice_blindedpay[0]->cltv_expiry_delta = ld->config.cltv_final; + inv->invoice_blindedpay[0]->htlc_minimum_msat = AMOUNT_MSAT(0); + inv->invoice_blindedpay[0]->htlc_maximum_msat = AMOUNT_MSAT(21000000 * MSAT_PER_BTC); + inv->invoice_blindedpay[0]->features = NULL; /* But we need to update ->fields, so re-linearize */ wire = tal_arr(tmpctx, u8, 0); @@ -1756,7 +1758,7 @@ static struct command_result *json_createinvoice(struct command *cmd, notify_invoice_creation(cmd->ld, b11->msat, *preimage, label); } else { struct tlv_invoice *inv; - struct sha256 *local_offer_id; + struct sha256 offer_id, *local_offer_id; char *b12enc; struct amount_msat msat; const char *desc; @@ -1771,10 +1773,16 @@ static struct command_result *json_createinvoice(struct command *cmd, "Unparsable invoice '%s': %s", invstring, fail); + /* BOLT-offers #12: + * A writer of an invoice: + *... + * - MUST include `invoice_paths` containing one or more paths + * to the node. + */ /* If they don't create a blinded path, add a simple one so we * can recognize payments (bolt12 doesn't use * payment_secret) */ - if (!inv->paths) + if (!inv->invoice_paths) inv = add_stub_blindedpath(cmd, cmd->ld, inv); if (inv->signature) @@ -1783,54 +1791,65 @@ static struct command_result *json_createinvoice(struct command *cmd, hsm_sign_b12_invoice(cmd->ld, inv); b12enc = invoice_encode(cmd, inv); - if (inv->offer_id - && wallet_offer_find(tmpctx, cmd->ld->wallet, - inv->offer_id, NULL, &status)) { - if (!offer_status_active(status)) - return command_fail(cmd, INVOICE_OFFER_INACTIVE, - "offer not active"); - local_offer_id = inv->offer_id; + if (inv->offer_node_id) { + invoice_offer_id(inv, &offer_id); + if (wallet_offer_find(tmpctx, cmd->ld->wallet, + &offer_id, NULL, &status)) { + if (!offer_status_active(status)) + return command_fail(cmd, + INVOICE_OFFER_INACTIVE, + "offer not active"); + local_offer_id = &offer_id; + } else + local_offer_id = NULL; } else local_offer_id = NULL; - if (inv->amount) - msat = amount_msat(*inv->amount); + /* BOLT-offers #12: + * A writer of an invoice: + *... + * - MUST set `invoice_amount` to the minimum amount it will + * accept, in units of the minimal lightning-payable unit + * (e.g. milli-satoshis for bitcoin) for `invreq_chain`. + */ + if (!inv->invoice_amount) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Missing invoice_amount in invoice"); + msat = amount_msat(*inv->invoice_amount); - if (inv->relative_expiry) - expiry = *inv->relative_expiry; + if (inv->invoice_relative_expiry) + expiry = *inv->invoice_relative_expiry; else expiry = BOLT12_DEFAULT_REL_EXPIRY; - if (!inv->payment_hash) + if (!inv->invoice_payment_hash) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Missing payment_hash in invoice"); - if (!sha256_eq(&payment_hash, inv->payment_hash)) + if (!sha256_eq(&payment_hash, inv->invoice_payment_hash)) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Incorrect preimage"); - if (!inv->description) + if (!inv->offer_description) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Missing description in invoice"); desc = tal_strndup(cmd, - cast_signed(char *, inv->description), - tal_bytelen(inv->description)); + inv->offer_description, + tal_bytelen(inv->offer_description)); if (!wallet_invoice_create(cmd->ld->wallet, &invoice, - inv->amount ? &msat : NULL, + &msat, label, expiry, b12enc, desc, - inv->features, + inv->invoice_features, preimage, &payment_hash, local_offer_id)) return fail_exists(cmd, label); - notify_invoice_creation(cmd->ld, - inv->amount ? &msat : NULL, - *preimage, label); + notify_invoice_creation(cmd->ld, &msat, *preimage, label); } response = json_stream_success(cmd); diff --git a/lightningd/offer.c b/lightningd/offer.c index c12f472aa9e6..bfa091a096bd 100644 --- a/lightningd/offer.c +++ b/lightningd/offer.c @@ -43,9 +43,6 @@ static struct command_result *param_b12_offer(struct command *cmd, cmd->ld->our_features, chainparams, &fail); if (!*offer) return command_fail_badparam(cmd, name, buffer, tok, fail); - if ((*offer)->signature) - return command_fail_badparam(cmd, name, buffer, tok, - "must be unsigned offer"); return NULL; } @@ -86,7 +83,7 @@ static struct command_result *json_createoffer(struct command *cmd, struct json_stream *response; struct json_escape *label; struct tlv_offer *offer; - struct sha256 merkle; + struct sha256 offer_id; const char *b12str; bool *single_use; enum offer_status status; @@ -103,15 +100,14 @@ static struct command_result *json_createoffer(struct command *cmd, status = OFFER_SINGLE_USE_UNUSED; else status = OFFER_MULTIPLE_USE_UNUSED; - merkle_tlv(offer->fields, &merkle); - offer->signature = NULL; b12str = offer_encode(cmd, offer); + offer_offer_id(offer, &offer_id); /* If it already exists, we use that one instead (and then * the offer plugin will complain if it's inactive or expired) */ - if (!wallet_offer_create(cmd->ld->wallet, &merkle, + if (!wallet_offer_create(cmd->ld->wallet, &offer_id, b12str, label, status)) { - if (!wallet_offer_find(cmd, cmd->ld->wallet, &merkle, + if (!wallet_offer_find(cmd, cmd->ld->wallet, &offer_id, cast_const2(const struct json_escape **, &label), &status)) { @@ -122,10 +118,8 @@ static struct command_result *json_createoffer(struct command *cmd, } else created = true; - offer->signature = tal_free(offer->signature); - response = json_stream_success(cmd); - json_populate_offer(response, &merkle, b12str, label, status); + json_populate_offer(response, &offer_id, b12str, label, status); json_add_bool(response, "created", created); return command_success(cmd, response); } @@ -240,7 +234,7 @@ static const struct json_command disableoffer_command = { AUTODATA(json_command, &disableoffer_command); /* We do some sanity checks now, since we're looking up prev payment anyway, - * but our main purpose is to fill in invreq->payer_info tweak. */ + * but our main purpose is to fill in invreq->invreq_metadata tweak. */ static struct command_result *prev_payment(struct command *cmd, const char *label, struct tlv_invoice_request *invreq, @@ -248,13 +242,16 @@ static struct command_result *prev_payment(struct command *cmd, { const struct wallet_payment **payments; bool prev_paid = false; + struct sha256 invreq_oid; - assert(!invreq->payer_info); + invreq_offer_id(invreq, &invreq_oid); + assert(!invreq->invreq_metadata); payments = wallet_payment_list(cmd, cmd->ld->wallet, NULL); for (size_t i = 0; i < tal_count(payments); i++) { const struct tlv_invoice *inv; char *fail; + struct sha256 inv_oid; /* FIXME: Restrict db queries instead */ if (!payments[i]->label || !streq(label, payments[i]->label)) @@ -270,12 +267,13 @@ static struct command_result *prev_payment(struct command *cmd, continue; /* They can reuse labels across different offers. */ - if (!sha256_eq(inv->offer_id, invreq->offer_id)) + invoice_offer_id(inv, &inv_oid); + if (!sha256_eq(&inv_oid, &invreq_oid)) continue; /* Be paranoid, in case someone inserts their own * clashing label! */ - if (!inv->recurrence_counter) + if (!inv->invreq_recurrence_counter) continue; /* BOLT-offers-recurrence #12: @@ -287,40 +285,40 @@ static struct command_result *prev_payment(struct command *cmd, * - MUST set `period_offset` to the same value on all * following requests. */ - if (invreq->recurrence_start) { - if (!inv->recurrence_start) + if (invreq->invreq_recurrence_start) { + if (!inv->invreq_recurrence_start) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "unexpected" " recurrence_start"); - if (*inv->recurrence_start != *invreq->recurrence_start) + if (*inv->invreq_recurrence_start != *invreq->invreq_recurrence_start) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "recurrence_start was" " previously %u", - *inv->recurrence_start); + *inv->invreq_recurrence_start); } else { - if (inv->recurrence_start) + if (inv->invreq_recurrence_start) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "missing" " recurrence_start"); } - if (*inv->recurrence_counter == *invreq->recurrence_counter-1) { + if (*inv->invreq_recurrence_counter == *invreq->invreq_recurrence_counter-1) { if (payments[i]->status == PAYMENT_COMPLETE) prev_paid = true; } - if (inv->payer_info) { - invreq->payer_info - = tal_dup_talarr(invreq, u8, inv->payer_info); + if (inv->invreq_metadata) { + invreq->invreq_metadata + = tal_dup_talarr(invreq, u8, inv->invreq_metadata); *prev_basetime = tal_dup(cmd, u64, - inv->recurrence_basetime); + inv->invoice_recurrence_basetime); } - if (prev_paid && inv->payer_info) + if (prev_paid && inv->invreq_metadata) break; } - if (!invreq->payer_info) + if (!invreq->invreq_metadata) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "No previous payment attempted for this" " label and offer"); @@ -347,13 +345,13 @@ static struct command_result *param_b12_invreq(struct command *cmd, return command_fail_badparam(cmd, name, buffer, tok, fail); #if !DEVELOPER /* We use this for testing with known payer_info */ - if ((*invreq)->payer_info) + if ((*invreq)->invreq_metadata) return command_fail_badparam(cmd, name, buffer, tok, - "must not have payer_info"); + "must not have invreq_metadata"); #endif - if ((*invreq)->payer_key) + if ((*invreq)->invreq_payer_id) return command_fail_badparam(cmd, name, buffer, tok, - "must not have payer_key"); + "must not have invreq_payer_id"); return NULL; } @@ -389,12 +387,14 @@ static struct command_result *json_createinvoicerequest(struct command *cmd, NULL)) return command_param_failed(); - if (invreq->recurrence_counter) { + /* If it's a recurring payment, we look for previous to copy + * invreq_metadata, basetime */ + if (invreq->invreq_recurrence_counter) { if (!label) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Need payment label for recurring payments"); - if (*invreq->recurrence_counter != 0) { + if (*invreq->invreq_recurrence_counter != 0) { struct command_result *err = prev_payment(cmd, label, invreq, &prev_basetime); @@ -403,25 +403,29 @@ static struct command_result *json_createinvoicerequest(struct command *cmd, } } - if (!invreq->payer_info) { + if (!invreq->invreq_metadata) { /* BOLT-offers #12: - * `payer_info` might typically contain information about the - * derivation of the `payer_key`. This should not leak any - * information (such as using a simple BIP-32 derivation - * path); a valid system might be for a node to maintain a - * base payer key, and encode a 128-bit tweak here. The - * payer_key would be derived by tweaking the base key with - * SHA256(payer_base_pubkey || tweak). + * + * `invreq_metadata` might typically contain information about + * the derivation of the `invreq_payer_id`. This should not + * leak any information (such as using a simple BIP-32 + * derivation path); a valid system might be for a node to + * maintain a base payer key and encode a 128-bit tweak here. + * The payer_id would be derived by tweaking the base key with + * SHA256(payer_base_pubkey || tweak). It's also the first + * entry (if present), ensuring an unpredictable nonce for + * hashing. */ - invreq->payer_info = tal_arr(invreq, u8, 16); - randombytes_buf(invreq->payer_info, - tal_bytelen(invreq->payer_info)); + invreq->invreq_metadata = tal_arr(invreq, u8, 16); + randombytes_buf(invreq->invreq_metadata, + tal_bytelen(invreq->invreq_metadata)); } - invreq->payer_key = tal(invreq, struct pubkey); + invreq->invreq_payer_id = tal(invreq, struct pubkey); if (!payer_key(cmd->ld, - invreq->payer_info, tal_bytelen(invreq->payer_info), - invreq->payer_key)) { + invreq->invreq_metadata, + tal_bytelen(invreq->invreq_metadata), + invreq->invreq_payer_id)) { return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Invalid tweak"); } @@ -435,7 +439,7 @@ static struct command_result *json_createinvoicerequest(struct command *cmd, merkle_tlv(invreq->fields, &merkle); invreq->signature = tal(invreq, struct bip340sig); hsm_sign_b12(cmd->ld, "invoice_request", "signature", - &merkle, invreq->payer_info, invreq->payer_key, + &merkle, invreq->invreq_metadata, invreq->invreq_payer_id, invreq->signature); response = json_stream_success(cmd); diff --git a/lightningd/pay.c b/lightningd/pay.c index 2277e99fa1f1..93cecd5d07e2 100644 --- a/lightningd/pay.c +++ b/lightningd/pay.c @@ -1592,8 +1592,8 @@ static struct command_result *json_listsendpays(struct command *cmd, b12 = invoice_decode(cmd, invstring, strlen(invstring), cmd->ld->our_features, chainparams, &fail); - if (b12 && b12->payment_hash) - rhash = b12->payment_hash; + if (b12 && b12->invoice_payment_hash) + rhash = b12->invoice_payment_hash; else return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Invalid invstring: %s", fail); diff --git a/lightningd/test/run-invoice-select-inchan.c b/lightningd/test/run-invoice-select-inchan.c index 0191cecf51f5..a38647e3851b 100644 --- a/lightningd/test/run-invoice-select-inchan.c +++ b/lightningd/test/run-invoice-select-inchan.c @@ -315,6 +315,9 @@ struct tlv_invoice *invoice_decode_nosig(const tal_t *ctx UNNEEDED, /* Generated stub for invoice_encode */ char *invoice_encode(const tal_t *ctx UNNEEDED, const struct tlv_invoice *bolt12_tlv UNNEEDED) { fprintf(stderr, "invoice_encode called!\n"); abort(); } +/* Generated stub for invoice_offer_id */ +void invoice_offer_id(const struct tlv_invoice *invoice UNNEEDED, struct sha256 *id UNNEEDED) +{ fprintf(stderr, "invoice_offer_id called!\n"); abort(); } /* Generated stub for invoice_path_id */ u8 *invoice_path_id(const tal_t *ctx UNNEEDED, const struct secret *base_secret UNNEEDED, diff --git a/plugins/bkpr/bookkeeper.c b/plugins/bkpr/bookkeeper.c index be0b256ef78c..e541d00674f4 100644 --- a/plugins/bkpr/bookkeeper.c +++ b/plugins/bkpr/bookkeeper.c @@ -1192,10 +1192,10 @@ static char *fetch_out_desc_invstr(const tal_t *ctx, const char *buf, return NULL; } - if (bolt12->description) + if (bolt12->offer_description) desc = tal_strndup(ctx, - cast_signed(char *, bolt12->description), - tal_bytelen(bolt12->description)); + cast_signed(char *, bolt12->offer_description), + tal_bytelen(bolt12->offer_description)); else desc = NULL; } else diff --git a/plugins/fetchinvoice.c b/plugins/fetchinvoice.c index bd581615ba40..19633f0943e1 100644 --- a/plugins/fetchinvoice.c +++ b/plugins/fetchinvoice.c @@ -59,37 +59,6 @@ static struct sent *find_sent_by_secret(const struct secret *pathsecret) return NULL; } -static const char *field_diff_(struct plugin *plugin, - const tal_t *a, const tal_t *b, - const char *fieldname) -{ - /* One is set and the other isn't? */ - if ((a == NULL) != (b == NULL)) { - plugin_log(plugin, LOG_DBG, "field_diff %s: a is %s, b is %s", - fieldname, a ? "set": "unset", b ? "set": "unset"); - return fieldname; - } - if (!memeq(a, tal_bytelen(a), b, tal_bytelen(b))) { - plugin_log(plugin, LOG_DBG, "field_diff %s: a=%s, b=%s", - fieldname, tal_hex(tmpctx, a), tal_hex(tmpctx, b)); - return fieldname; - } - return NULL; -} - -#define field_diff(a, b, fieldname) \ - field_diff_((cmd)->plugin, a->fieldname, b->fieldname, #fieldname) - -/* Returns true if b is a with something appended. */ -static bool description_is_appended(const char *a, const char *b) -{ - if (!a || !b) - return false; - if (tal_bytelen(b) < tal_bytelen(a)) - return false; - return memeq(a, tal_bytelen(a), b, tal_bytelen(a)); -} - /* Hack to suppress warnings when we finish a different command */ static void discard_result(struct command_result *ret) { @@ -155,12 +124,33 @@ static struct command_result *handle_error(struct command *cmd, return command_hook_success(cmd); } +/* BOLT-offers #12: + * - if the invoice is a response to an `invoice_request`: + * - MUST reject the invoice if all fields less than type 160 do not + * exactly match the `invoice_request`. + */ +static bool invoice_matches_request(struct command *cmd, + const u8 *invbin, + const struct tlv_invoice_request *invreq) +{ + size_t len1, len2; + u8 *wire; + + /* We linearize then strip signature. This is dumb! */ + wire = tal_arr(tmpctx, u8, 0); + towire_tlv_invoice_request(&wire, invreq); + len1 = tlv_span(wire, 0, 159, NULL); + + len2 = tlv_span(invbin, 0, 159, NULL); + return memeq(wire, len1, invbin, len2); +} + static struct command_result *handle_invreq_response(struct command *cmd, struct sent *sent, const char *buf, const jsmntok_t *om) { - const u8 *invbin; + const u8 *invbin, *cursor; const jsmntok_t *invtok; size_t len; struct tlv_invoice *inv; @@ -184,8 +174,9 @@ static struct command_result *handle_invreq_response(struct command *cmd, } invbin = json_tok_bin_from_hex(cmd, buf, invtok); + cursor = invbin; len = tal_bytelen(invbin); - inv = fromwire_tlv_invoice(cmd, &invbin, &len); + inv = fromwire_tlv_invoice(cmd, &cursor, &len); if (!inv) { badfield = "invoice"; goto badinv; @@ -202,88 +193,64 @@ static struct command_result *handle_invreq_response(struct command *cmd, #endif /* DEVELOPER */ /* BOLT-offers #12: - * - MUST reject the invoice unless `node_id` is equal to the offer. + * - if the invoice is a response to an `invoice_request`: + * - MUST reject the invoice if all fields less than type 160 do not + * exactly match the `invoice_request`. */ - if (!pubkey_eq(sent->offer->node_id, inv->node_id)) { - badfield = "node_id"; + if (!invoice_matches_request(cmd, invbin, sent->invreq)) { + badfield = "invoice_request match"; + goto badinv; + } + + /* BOLT-offers #12: + * - if `offer_node_id` is present (invoice_request for an offer): + * - MUST reject the invoice if `invoice_node_id` is not equal to `offer_node_id`. + */ + if (!inv->invoice_node_id || !pubkey_eq(inv->offer_node_id, inv->invoice_node_id)) { + badfield = "invoice_node_id"; goto badinv; } /* BOLT-offers #12: * - MUST reject the invoice if `signature` is not a valid signature - * using `node_id` as described in [Signature Calculation] + * using `invoice_node_id` as described in [Signature Calculation] */ merkle_tlv(inv->fields, &merkle); sighash_from_merkle("invoice", "signature", &merkle, &sighash); if (!inv->signature - || !check_schnorr_sig(&sighash, &inv->node_id->pubkey, inv->signature)) { + || !check_schnorr_sig(&sighash, &inv->invoice_node_id->pubkey, inv->signature)) { badfield = "signature"; goto badinv; } /* BOLT-offers #12: - * - MUST reject the invoice if `msat` is not present. + * A reader of an invoice: + * - MUST reject the invoice if `invoice_amount` is not present. */ - if (!inv->amount) { - badfield = "amount"; + if (!inv->invoice_amount) { + badfield = "invoice_amount"; goto badinv; } - /* BOLT-offers #12: - * - MUST reject the invoice unless `offer_id` is equal to the id of the - * offer. - */ - if ((badfield = field_diff(sent->invreq, inv, offer_id))) - goto badinv; - - /* BOLT-offers #12: - * - if the invoice is a reply to an `invoice_request`: - *... - * - MUST reject the invoice unless the following fields are equal or - * unset exactly as they are in the `invoice_request:` - * - `quantity` - * - `payer_key` - * - `payer_info` - */ - /* BOLT-offers-recurrence #12: - * - if the invoice is a reply to an `invoice_request`: - *... - * - MUST reject the invoice unless the following fields are equal or - * unset exactly as they are in the `invoice_request:` - * - `quantity` - * - `recurrence_counter` - * - `recurrence_start` - * - `payer_key` - * - `payer_info` - */ - if ((badfield = field_diff(sent->invreq, inv, quantity))) - goto badinv; - if ((badfield = field_diff(sent->invreq, inv, recurrence_counter))) - goto badinv; - if ((badfield = field_diff(sent->invreq, inv, recurrence_start))) - goto badinv; - if ((badfield = field_diff(sent->invreq, inv, payer_key))) - goto badinv; - if ((badfield = field_diff(sent->invreq, inv, payer_info))) - goto badinv; + /* FIXME-OFFERS: Examine fields in inv directly! */ /* Get the amount we expected: firstly, if that's what we sent, * secondly, if specified in the invoice. */ - if (sent->invreq->amount) { - expected_amount = tal_dup(tmpctx, u64, sent->invreq->amount); - } else if (sent->offer->amount && !sent->offer->currency) { + if (sent->invreq->invreq_amount) { + expected_amount = tal_dup(tmpctx, u64, sent->invreq->invreq_amount); + } else if (sent->offer->offer_amount && !sent->offer->offer_currency) { expected_amount = tal(tmpctx, u64); - *expected_amount = *sent->offer->amount; - if (sent->invreq->quantity) { + *expected_amount = *sent->offer->offer_amount; + if (sent->invreq->invreq_quantity) { /* We should never have sent this! */ if (mul_overflows_u64(*expected_amount, - *sent->invreq->quantity)) { + *sent->invreq->invreq_quantity)) { badfield = "quantity overflow"; goto badinv; } - *expected_amount *= *sent->invreq->quantity; + *expected_amount *= *sent->invreq->invreq_quantity; } } else expected_amount = NULL; @@ -292,97 +259,56 @@ static struct command_result *handle_invreq_response(struct command *cmd, * - if the offer contained `recurrence`: * - MUST reject the invoice if `recurrence_basetime` is not set. */ - if (sent->invreq->recurrence_counter && !inv->recurrence_basetime) { + if (sent->invreq->invreq_recurrence_counter && !inv->invoice_recurrence_basetime) { badfield = "recurrence_basetime"; goto badinv; } - /* BOLT-offers #12: - * - SHOULD confirm authorization if the `description` does not exactly - * match the `offer` - * - MAY highlight if `description` has simply had a change appended. - */ - /* We highlight these changes to the caller, for them to handle */ out = jsonrpc_stream_success(sent->cmd); json_add_string(out, "invoice", invoice_encode(tmpctx, inv)); json_object_start(out, "changes"); - if (field_diff(sent->offer, inv, description)) { - /* Did they simply append? */ - if (description_is_appended(sent->offer->description, - inv->description)) { - size_t off = tal_bytelen(sent->offer->description); - json_add_stringn(out, "description_appended", - inv->description + off, - tal_bytelen(inv->description) - off); - } else if (!inv->description) - json_add_stringn(out, "description_removed", - sent->offer->description, - tal_bytelen(sent->offer->description)); - else - json_add_stringn(out, "description", - inv->description, - tal_bytelen(inv->description)); - } - - /* BOLT-offers #12: - * - SHOULD confirm authorization if `issuer` does not exactly - * match the `offer` - */ - if (field_diff(sent->offer, inv, issuer)) { - if (!inv->issuer) - json_add_stringn(out, "issuer_removed", - sent->offer->issuer, - tal_bytelen(sent->offer->issuer)); - else - json_add_stringn(out, "issuer", - inv->issuer, - tal_bytelen(inv->issuer)); - } /* BOLT-offers #12: * - SHOULD confirm authorization if `msat` is not within the amount * range authorized. */ /* We always tell them this unless it's trivial to calc and * exactly as expected. */ - if (!expected_amount || *inv->amount != *expected_amount) { - if (deprecated_apis) - json_add_amount_msat_only(out, "msat", - amount_msat(*inv->amount)); + if (!expected_amount || *inv->invoice_amount != *expected_amount) { json_add_amount_msat_only(out, "amount_msat", - amount_msat(*inv->amount)); + amount_msat(*inv->invoice_amount)); } json_object_end(out); /* We tell them about next period at this point, if any. */ - if (sent->offer->recurrence) { + if (sent->offer->offer_recurrence) { u64 next_counter, next_period_idx; u64 paywindow_start, paywindow_end; - next_counter = *sent->invreq->recurrence_counter + 1; - if (sent->invreq->recurrence_start) - next_period_idx = *sent->invreq->recurrence_start + next_counter = *sent->invreq->invreq_recurrence_counter + 1; + if (sent->invreq->invreq_recurrence_start) + next_period_idx = *sent->invreq->invreq_recurrence_start + next_counter; else next_period_idx = next_counter; /* If this was the last, don't tell them about a next! */ - if (!sent->offer->recurrence_limit - || next_period_idx <= *sent->offer->recurrence_limit) { + if (!sent->offer->offer_recurrence_limit + || next_period_idx <= *sent->offer->offer_recurrence_limit) { json_object_start(out, "next_period"); json_add_u64(out, "counter", next_counter); json_add_u64(out, "starttime", - offer_period_start(*inv->recurrence_basetime, + offer_period_start(*inv->invoice_recurrence_basetime, next_period_idx, - sent->offer->recurrence)); + sent->offer->offer_recurrence)); json_add_u64(out, "endtime", - offer_period_start(*inv->recurrence_basetime, + offer_period_start(*inv->invoice_recurrence_basetime, next_period_idx + 1, - sent->offer->recurrence) - 1); + sent->offer->offer_recurrence) - 1); - offer_period_paywindow(sent->offer->recurrence, - sent->offer->recurrence_paywindow, - sent->offer->recurrence_base, - *inv->recurrence_basetime, + offer_period_paywindow(sent->offer->offer_recurrence, + sent->offer->offer_recurrence_paywindow, + sent->offer->offer_recurrence_base, + *inv->invoice_recurrence_basetime, next_period_idx, &paywindow_start, &paywindow_end); json_add_u64(out, "paywindow_start", paywindow_start); @@ -500,18 +426,8 @@ static struct command_result *param_offer(struct command *cmd, struct tlv_offer **offer) { char *fail; + int badf; - /* BOLT-offers #12: - * - if `features` contains unknown _odd_ bits that are non-zero: - * - MUST ignore the bit. - * - if `features` contains unknown _even_ bits that are non-zero: - * - MUST NOT respond to the offer. - * - SHOULD indicate the unknown bit to the user. - */ - /* BOLT-offers #12: - * - MUST NOT set or imply any `chain_hash` not set or implied by - * the offer. - */ *offer = offer_decode(cmd, buffer + tok->start, tok->end - tok->start, plugin_feature_set(cmd->plugin), chainparams, &fail); @@ -520,19 +436,61 @@ static struct command_result *param_offer(struct command *cmd, tal_fmt(cmd, "Unparsable offer: %s", fail)); - /* BOLT-offers #12: - * - * - if `node_id` or `description` is not set: - * - MUST NOT respond to the offer. + * A reader of an offer: + * - if the offer contains any unknown 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. */ - if (!(*offer)->node_id) + for (size_t i = 0; i < tal_count((*offer)->fields); i++) { + if ((*offer)->fields[i].numtype > 80) { + return command_fail_badparam(cmd, name, buffer, tok, + tal_fmt(tmpctx, + "Offer %"PRIu64 + " field >= 80", + (*offer)->fields[i].numtype)); + } + } + + badf = features_unsupported(plugin_feature_set(cmd->plugin), + (*offer)->offer_features, + BOLT12_OFFER_FEATURE); + if (badf != -1) { + return command_fail_badparam(cmd, name, buffer, tok, + tal_fmt(tmpctx, + "unknown feature %i", + badf)); + } + if (!(*offer)->offer_description) + return command_fail_badparam(cmd, name, buffer, tok, + "Offer does not contain a description"); + if (!(*offer)->offer_node_id) return command_fail_badparam(cmd, name, buffer, tok, "Offer does not contain a node_id"); - if (!(*offer)->description) + /* BOLT-offers #12: + * - 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 (!bolt12_chains_match((*offer)->offer_chains, + tal_count((*offer)->offer_chains), + chainparams)) { return command_fail_badparam(cmd, name, buffer, tok, - "Offer does not contain a description"); + "Offer for wrong chains"); + } + return NULL; } @@ -875,26 +833,26 @@ static struct command_result *invreq_done(struct command *cmd, /* Now that's given us the previous base, check this is an OK time * to request an invoice. */ - if (sent->invreq->recurrence_counter) { + if (sent->invreq->invreq_recurrence_counter) { u64 *base; const jsmntok_t *pbtok; - u64 period_idx = *sent->invreq->recurrence_counter; + u64 period_idx = *sent->invreq->invreq_recurrence_counter; - if (sent->invreq->recurrence_start) - period_idx += *sent->invreq->recurrence_start; + if (sent->invreq->invreq_recurrence_start) + period_idx += *sent->invreq->invreq_recurrence_start; /* BOLT-offers-recurrence #12: * - if the offer contained `recurrence_limit`: * - MUST NOT send an `invoice_request` for a period greater * than `max_period` */ - if (sent->offer->recurrence_limit - && period_idx > *sent->offer->recurrence_limit) + if (sent->offer->offer_recurrence_limit + && period_idx > *sent->offer->offer_recurrence_limit) return command_fail(cmd, LIGHTNINGD, "Can't send invreq for period %" PRIu64" (limit %u)", period_idx, - *sent->offer->recurrence_limit); + *sent->offer->offer_recurrence_limit); /* BOLT-offers-recurrence #12: * - SHOULD NOT send an `invoice_request` for a period which has @@ -907,19 +865,19 @@ static struct command_result *invreq_done(struct command *cmd, if (pbtok) { base = tal(tmpctx, u64); json_to_u64(buf, pbtok, base); - } else if (sent->offer->recurrence_base) - base = &sent->offer->recurrence_base->basetime; + } else if (sent->offer->offer_recurrence_base) + base = &sent->offer->offer_recurrence_base->basetime; else { /* happens with *recurrence_base == 0 */ - assert(*sent->invreq->recurrence_counter == 0); + assert(*sent->invreq->invreq_recurrence_counter == 0); base = NULL; } if (base) { u64 period_start, period_end, now = time_now().ts.tv_sec; - offer_period_paywindow(sent->offer->recurrence, - sent->offer->recurrence_paywindow, - sent->offer->recurrence_base, + offer_period_paywindow(sent->offer->offer_recurrence, + sent->offer->offer_recurrence_paywindow, + sent->offer->offer_recurrence_base, *base, period_idx, &period_start, &period_end); if (now < period_start) @@ -938,9 +896,9 @@ static struct command_result *invreq_done(struct command *cmd, } sent->path = path_to_node(sent, cmd->plugin, - sent->offer->node_id); + sent->offer->offer_node_id); if (!sent->path) - return connect_direct(cmd, sent->offer->node_id, + return connect_direct(cmd, sent->offer->offer_node_id, sendinvreq_after_connect, sent); return sendinvreq_after_connect(cmd, NULL, NULL, sent); @@ -963,10 +921,10 @@ force_payer_secret(struct command *cmd, if (secp256k1_keypair_create(secp256k1_ctx, &kp, payer_secret->data) != 1) return command_fail(cmd, LIGHTNINGD, "Bad payer_secret"); - invreq->payer_key = tal(invreq, struct pubkey); + invreq->invreq_payer_id = tal(invreq, struct pubkey); /* Docs say this only happens if arguments are invalid! */ if (secp256k1_keypair_pub(secp256k1_ctx, - &invreq->payer_key->pubkey, + &invreq->invreq_payer_id->pubkey, &kp) != 1) plugin_err(cmd->plugin, "secp256k1_keypair_pub failed on %s?", @@ -996,9 +954,9 @@ force_payer_secret(struct command *cmd, } sent->path = path_to_node(sent, cmd->plugin, - sent->offer->node_id); + sent->offer->offer_node_id); if (!sent->path) - return connect_direct(cmd, sent->offer->node_id, + return connect_direct(cmd, sent->offer->offer_node_id, sendinvreq_after_connect, sent); return sendinvreq_after_connect(cmd, NULL, NULL, sent); @@ -1016,16 +974,15 @@ static struct command_result *json_fetchinvoice(struct command *cmd, struct sent *sent = tal(cmd, struct sent); struct secret *payer_secret = NULL; u32 *timeout; + u64 *quantity; + u32 *recurrence_counter, *recurrence_start; - invreq = tlv_invoice_request_new(sent); if (!param(cmd, buffer, params, p_req("offer", param_offer, &sent->offer), p_opt("amount_msat|msatoshi", param_msat, &msat), - p_opt("quantity", param_u64, &invreq->quantity), - p_opt("recurrence_counter", param_number, - &invreq->recurrence_counter), - p_opt("recurrence_start", param_number, - &invreq->recurrence_start), + p_opt("quantity", param_u64, &quantity), + p_opt("recurrence_counter", param_number, &recurrence_counter), + p_opt("recurrence_start", param_number, &recurrence_start), p_opt("recurrence_label", param_string, &rec_label), p_opt_def("timeout", param_number, &timeout, 60), p_opt("payer_note", param_string, &payer_note), @@ -1037,74 +994,66 @@ static struct command_result *json_fetchinvoice(struct command *cmd, sent->wait_timeout = *timeout; - /* BOLT-offers #12: - * - MUST set `offer_id` to the Merkle root of the offer as described - * in [Signature Calculation](#signature-calculation). - */ - invreq->offer_id = tal(invreq, struct sha256); - merkle_tlv(sent->offer->fields, invreq->offer_id); - - /* Check if they are trying to send us money. */ - if (sent->offer->send_invoice) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "Offer wants an invoice, not invoice_request"); - /* BOLT-offers #12: * - SHOULD not respond to an offer if the current time is after * `absolute_expiry`. */ - if (sent->offer->absolute_expiry - && time_now().ts.tv_sec > *sent->offer->absolute_expiry) + if (sent->offer->offer_absolute_expiry + && time_now().ts.tv_sec > *sent->offer->offer_absolute_expiry) return command_fail(cmd, OFFER_EXPIRED, "Offer expired"); + /* BOLT-offers #12: + * The writer: + * - if it is responding to an offer: + * - MUST copy all fields from the offer (including unknown fields). + */ + invreq = invoice_request_for_offer(sent, sent->offer); + invreq->invreq_recurrence_counter = tal_steal(invreq, recurrence_counter); + invreq->invreq_recurrence_start = tal_steal(invreq, recurrence_start); + /* BOLT-offers-recurrence #12: - * - if the offer did not specify `amount`: - * - MUST specify `amount`.`msat` in multiples of the minimum - * lightning-payable unit (e.g. milli-satoshis for bitcoin) for - * `chain` (or for bitcoin, if there is no `chain`). - * - otherwise: - * - MAY omit `amount`. - * - if it sets `amount`: - * - MUST specify `amount`.`msat` as greater or equal to amount - * expected by the offer (before any proportional period amount). + * - if `offer_amount` is not present: + * - MUST specify `invreq_amount`. + * - otherwise: + * - MAY omit `invreq_amount`. + * - if it sets `invreq_amount`: + * - MUST specify `invreq_amount`.`msat` as greater or equal to + * amount expected by `offer_amount` (and, if present, + * `offer_currency` and `invreq_quantity`). */ - if (sent->offer->amount) { + if (sent->offer->offer_amount) { /* FIXME: Check after quantity? */ if (msat) { - invreq->amount = tal_dup(invreq, u64, - &msat->millisatoshis); /* Raw: tu64 */ + invreq->invreq_amount = tal_dup(invreq, u64, + &msat->millisatoshis); /* Raw: tu64 */ } } else { if (!msat) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "msatoshi parameter required"); - invreq->amount = tal_dup(invreq, u64, - &msat->millisatoshis); /* Raw: tu64 */ + invreq->invreq_amount = tal_dup(invreq, u64, + &msat->millisatoshis); /* Raw: tu64 */ } + /* FIXME-OFFERS: Examine fields in inv directly! */ /* BOLT-offers #12: - * - if the offer had a `quantity_min` or `quantity_max` field: - * - MUST set `quantity` - * - MUST set it within that (inclusive) range. - * - otherwise: - * - MUST NOT set `quantity` + * - if `offer_quantity_max` is present: + * - MUST set `invreq_quantity` + * - if `offer_quantity_max` is non-zero: + * - MUST set `invreq_quantity` less than or equal to + * `offer_quantity_max`. */ - if (sent->offer->quantity_min || sent->offer->quantity_max) { - if (!invreq->quantity) + if (sent->offer->offer_quantity_max) { + if (!invreq->invreq_quantity) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "quantity parameter required"); - if (sent->offer->quantity_min - && *invreq->quantity < *sent->offer->quantity_min) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "quantity must be >= %"PRIu64, - *sent->offer->quantity_min); - if (sent->offer->quantity_max - && *invreq->quantity > *sent->offer->quantity_max) + if (*sent->offer->offer_quantity_max + && *invreq->invreq_quantity > *sent->offer->offer_quantity_max) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "quantity must be <= %"PRIu64, - *sent->offer->quantity_max); + *sent->offer->offer_quantity_max); } else { - if (invreq->quantity) + if (invreq->invreq_quantity) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "quantity parameter unnecessary"); } @@ -1112,7 +1061,7 @@ static struct command_result *json_fetchinvoice(struct command *cmd, /* BOLT-offers-recurrence #12: * - if the offer contained `recurrence`: */ - if (sent->offer->recurrence) { + if (sent->offer->offer_recurrence) { /* BOLT-offers-recurrence #12: * - for the initial request: *... @@ -1124,7 +1073,7 @@ static struct command_result *json_fetchinvoice(struct command *cmd, * - MUST set `recurrence_counter` `counter` to one greater * than the highest-paid invoice. */ - if (!invreq->recurrence_counter) + if (!invreq->invreq_recurrence_counter) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "needs recurrence_counter"); @@ -1136,13 +1085,13 @@ static struct command_result *json_fetchinvoice(struct command *cmd, * - otherwise: * - MUST NOT include `recurrence_start` */ - if (sent->offer->recurrence_base - && sent->offer->recurrence_base->start_any_period) { - if (!invreq->recurrence_start) + if (sent->offer->offer_recurrence_base + && sent->offer->offer_recurrence_base->start_any_period) { + if (!invreq->invreq_recurrence_start) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "needs recurrence_start"); } else { - if (invreq->recurrence_start) + if (invreq->invreq_recurrence_start) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "unnecessary recurrence_start"); } @@ -1158,34 +1107,41 @@ static struct command_result *json_fetchinvoice(struct command *cmd, * - MUST NOT set `recurrence_counter`. * - MUST NOT set `recurrence_start` */ - if (invreq->recurrence_counter) + if (invreq->invreq_recurrence_counter) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "unnecessary recurrence_counter"); - if (invreq->recurrence_start) + if (invreq->invreq_recurrence_start) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "unnecessary recurrence_start"); } /* BOLT-offers #12: * - * - if the chain for the invoice is not solely bitcoin: - * - MUST specify `chains` the offer is valid for. + * - if `offer_chains` is set: + * - MUST set `invreq_chain` to one of `offer_chains` unless that + * chain is bitcoin, in which case it MAY omit `invreq_chain`. * - otherwise: - * - the bitcoin chain is implied as the first and only entry. + * - if it sets `invreq_chain` it MUST set it to bitcoin. */ + /* We already checked that we're compatible chain, in param_offer */ if (!streq(chainparams->network_name, "bitcoin")) { - invreq->chain = tal_dup(invreq, struct bitcoin_blkid, - &chainparams->genesis_blockhash); + invreq->invreq_chain = tal_dup(invreq, struct bitcoin_blkid, + &chainparams->genesis_blockhash); } - invreq->features - = plugin_feature_set(cmd->plugin)->bits[BOLT11_FEATURE]; + /* BOLT-offers #12: + * - if it supports bolt12 invoice request features: + * - MUST set `invreq_features`.`features` to the bitmap of features. + */ + invreq->invreq_features + = plugin_feature_set(cmd->plugin)->bits[BOLT12_OFFER_FEATURE]; /* invreq->payer_note is not a nul-terminated string! */ if (payer_note) - invreq->payer_note = tal_dup_arr(invreq, utf8, - payer_note, strlen(payer_note), - 0); + invreq->invreq_payer_note = tal_dup_arr(invreq, utf8, + payer_note, + strlen(payer_note), + 0); /* They can provide a secret, and we don't assume it's our job * to pay. */ diff --git a/plugins/offers.c b/plugins/offers.c index 040b7f038b35..f26c320c1934 100644 --- a/plugins/offers.c +++ b/plugins/offers.c @@ -317,100 +317,97 @@ static void json_add_offer(struct json_stream *js, const struct tlv_offer *offer struct sha256 offer_id; bool valid = true; - merkle_tlv(offer->fields, &offer_id); + /* FIXME-OFFERS: Rename all fields to offer_ as per spec */ + offer_offer_id(offer, &offer_id); json_add_sha256(js, "offer_id", &offer_id); - if (offer->chains) - json_add_chains(js, offer->chains); - if (offer->currency) { + if (offer->offer_chains) + json_add_chains(js, offer->offer_chains); + if (offer->offer_currency) { const struct iso4217_name_and_divisor *iso4217; json_add_stringn(js, "currency", - offer->currency, tal_bytelen(offer->currency)); - if (offer->amount) - json_add_u64(js, "amount", *offer->amount); - iso4217 = find_iso4217(offer->currency, - tal_bytelen(offer->currency)); + offer->offer_currency, + tal_bytelen(offer->offer_currency)); + if (offer->offer_amount) + json_add_u64(js, "amount", *offer->offer_amount); + iso4217 = find_iso4217(offer->offer_currency, + tal_bytelen(offer->offer_currency)); if (iso4217) json_add_num(js, "minor_unit", iso4217->minor_unit); else json_add_string(js, "warning_offer_unknown_currency", "unknown currency code"); - } else if (offer->amount) + } else if (offer->offer_amount) json_add_amount_msat_only(js, "amount_msat", - amount_msat(*offer->amount)); - if (offer->send_invoice) - json_add_bool(js, "send_invoice", true); - if (offer->refund_for) - json_add_sha256(js, "refund_for", offer->refund_for); + amount_msat(*offer->offer_amount)); /* BOLT-offers #12: * A reader of an offer: *... - * - if `node_id` or `description` is not set: - * - MUST NOT respond to the offer. + * - if `offer_description` is not set: + * - MUST NOT respond to the offer. */ - if (offer->description) + if (offer->offer_description) json_add_stringn(js, "description", - offer->description, - tal_bytelen(offer->description)); + offer->offer_description, + tal_bytelen(offer->offer_description)); else { json_add_string(js, "warning_offer_missing_description", "offers without a description are invalid"); valid = false; } - if (offer->issuer) - json_add_stringn(js, "issuer", offer->issuer, - tal_bytelen(offer->issuer)); - if (offer->features) - json_add_hex_talarr(js, "features", offer->features); - if (offer->absolute_expiry) + if (offer->offer_issuer) + json_add_stringn(js, "issuer", offer->offer_issuer, + tal_bytelen(offer->offer_issuer)); + if (offer->offer_features) + json_add_hex_talarr(js, "features", offer->offer_features); + if (offer->offer_absolute_expiry) json_add_u64(js, "absolute_expiry", - *offer->absolute_expiry); - if (offer->paths) - valid &= json_add_blinded_paths(js, offer->paths, NULL); - - if (offer->quantity_min) - json_add_u64(js, "quantity_min", *offer->quantity_min); - if (offer->quantity_max) - json_add_u64(js, "quantity_max", *offer->quantity_max); - if (offer->recurrence) { + *offer->offer_absolute_expiry); + if (offer->offer_paths) + valid &= json_add_blinded_paths(js, offer->offer_paths, NULL); + + if (offer->offer_quantity_max) + json_add_u64(js, "quantity_max", *offer->offer_quantity_max); + if (offer->offer_recurrence) { const char *name; json_object_start(js, "recurrence"); - json_add_num(js, "time_unit", offer->recurrence->time_unit); - name = recurrence_time_unit_name(offer->recurrence->time_unit); + json_add_num(js, "time_unit", offer->offer_recurrence->time_unit); + name = recurrence_time_unit_name(offer->offer_recurrence->time_unit); if (name) json_add_string(js, "time_unit_name", name); - json_add_num(js, "period", offer->recurrence->period); - if (offer->recurrence_base) { + json_add_num(js, "period", offer->offer_recurrence->period); + if (offer->offer_recurrence_base) { json_add_u64(js, "basetime", - offer->recurrence_base->basetime); - if (offer->recurrence_base->start_any_period) + offer->offer_recurrence_base->basetime); + if (offer->offer_recurrence_base->start_any_period) json_add_bool(js, "start_any_period", true); } - if (offer->recurrence_limit) - json_add_u32(js, "limit", *offer->recurrence_limit); - if (offer->recurrence_paywindow) { + if (offer->offer_recurrence_limit) + json_add_u32(js, "limit", *offer->offer_recurrence_limit); + if (offer->offer_recurrence_paywindow) { json_object_start(js, "paywindow"); json_add_u32(js, "seconds_before", - offer->recurrence_paywindow->seconds_before); + offer->offer_recurrence_paywindow->seconds_before); json_add_u32(js, "seconds_after", - offer->recurrence_paywindow->seconds_after); - if (offer->recurrence_paywindow->proportional_amount) + offer->offer_recurrence_paywindow->seconds_after); + if (offer->offer_recurrence_paywindow->proportional_amount) json_add_bool(js, "proportional_amount", true); json_object_end(js); } json_object_end(js); } - if (offer->node_id) - json_add_pubkey(js, "node_id", offer->node_id); + /* BOLT-offers #12: + * - if `offer_node_id` is not set: + * - MUST NOT respond to the offer. + */ + /* FIXME-OFFERS: Rename all fields to offer_ as per spec */ + if (offer->offer_node_id) + json_add_pubkey(js, "node_id", offer->offer_node_id); else valid = false; - /* If it's present, offer_decode checked it was valid */ - if (offer->signature) - json_add_bip340sig(js, "signature", offer->signature); - json_add_bool(js, "valid", valid); } @@ -491,41 +488,83 @@ static void json_add_b12_invoice(struct json_stream *js, { bool valid = true; - if (invoice->chain) - json_add_sha256(js, "chain", &invoice->chain->shad.sha); - if (invoice->offer_id) - json_add_sha256(js, "offer_id", invoice->offer_id); + /* If there's an offer_node_id, then there's an offer. */ + if (invoice->offer_node_id) { + struct sha256 offer_id; + + invoice_offer_id(invoice, &offer_id); + json_add_sha256(js, "offer_id", &offer_id); + } + + /* FIXME-OFFERS: Rename all fields to invoice_ as per spec */ + if (invoice->invreq_chain) + json_add_sha256(js, "chain", &invoice->invreq_chain->shad.sha); /* BOLT-offers #12: - * - MUST reject the invoice if `msat` is not present. + * - MUST reject the invoice if `invoice_amount` is not present. + * - MUST reject the invoice if `invreq_payer_id` is not present. */ - if (invoice->amount) + if (invoice->invoice_amount) json_add_amount_msat_only(js, "amount_msat", - amount_msat(*invoice->amount)); + amount_msat(*invoice->invoice_amount)); else { json_add_string(js, "warning_invoice_missing_amount", "invoices without an amount are invalid"); valid = false; } + if (invoice->invreq_payer_id) + json_add_pubkey(js, "payer_key", invoice->invreq_payer_id); + else { + json_add_string(js, "warning_invoice_missing_invreq_payer_id", + "invoices without an invreq_payer_id are invald"); + valid = false; + } + /* BOLT-offers #12: - * - MUST reject the invoice if `description` is not present. + * - MUST reject the invoice if `offer_description` is not present. + * - MUST reject the invoice if `invoice_created_at` is not present. + * - MUST reject the invoice if `invoice_payment_hash` is not present. */ - if (invoice->description) - json_add_stringn(js, "description", invoice->description, - tal_bytelen(invoice->description)); + if (invoice->offer_description) + json_add_stringn(js, "description", invoice->offer_description, + tal_bytelen(invoice->offer_description)); else { json_add_string(js, "warning_invoice_missing_description", "invoices without a description are invalid"); valid = false; } - if (invoice->issuer) - json_add_stringn(js, "issuer", invoice->issuer, - tal_bytelen(invoice->issuer)); - if (invoice->features) - json_add_hex_talarr(js, "features", invoice->features); - if (invoice->paths) { + if (invoice->invoice_created_at) { + json_add_u64(js, "created_at", *invoice->invoice_created_at); + } else { + json_add_string(js, "warning_invoice_missing_created_at", + "invoices without created_at are invalid"); + valid = false; + } + + if (invoice->invoice_payment_hash) + json_add_sha256(js, "payment_hash", invoice->invoice_payment_hash); + else { + json_add_string(js, "warning_invoice_missing_payment_hash", + "invoices without a payment_hash are invalid"); + valid = false; + } + + if (invoice->offer_issuer) + json_add_stringn(js, "issuer", invoice->offer_issuer, + tal_bytelen(invoice->offer_issuer)); + if (invoice->invoice_features) + json_add_hex_talarr(js, "features", invoice->invoice_features); + + /* BOLT-offers #12: + * - MUST reject the invoice if `invoice_paths` is not present + * or is empty. + * - MUST reject the invoice if `invoice_blindedpay` is not present. + * - MUST reject the invoice if `invoice_blindedpay` does not contain + * 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 @@ -534,33 +573,35 @@ static void json_add_b12_invoice(struct json_stream *js, * contain exactly as many `payinfo` as total `onionmsg_path` * in `blinded_path`. */ - if (!invoice->blindedpay) { + if (!invoice->invoice_blindedpay) { json_add_string(js, "warning_invoice_missing_blinded_payinfo", "invoices with blinded_path without blinded_payinfo are invalid"); valid = false; } - valid &= json_add_blinded_paths(js, invoice->paths, invoice->blindedpay); + valid &= json_add_blinded_paths(js, invoice->invoice_paths, + invoice->invoice_blindedpay); + } else { + json_add_string(js, "warning_invoice_missing_blinded_path", + "invoices without a payment_hash are invalid"); + valid = false; } - if (invoice->quantity) - json_add_u64(js, "quantity", *invoice->quantity); - if (invoice->send_invoice) - json_add_bool(js, "send_invoice", true); - if (invoice->refund_for) - json_add_sha256(js, "refund_for", invoice->refund_for); - if (invoice->recurrence_counter) { + + if (invoice->invreq_quantity) + json_add_u64(js, "quantity", *invoice->invreq_quantity); + if (invoice->invreq_recurrence_counter) { json_add_u32(js, "recurrence_counter", - *invoice->recurrence_counter); - if (invoice->recurrence_start) + *invoice->invreq_recurrence_counter); + if (invoice->invreq_recurrence_start) json_add_u32(js, "recurrence_start", - *invoice->recurrence_start); + *invoice->invreq_recurrence_start); /* BOLT-offers-recurrence #12: * - if the offer contained `recurrence`: * - MUST reject the invoice if `recurrence_basetime` is not * set. */ - if (invoice->recurrence_basetime) + if (invoice->invoice_recurrence_basetime) json_add_u64(js, "recurrence_basetime", - *invoice->recurrence_basetime); + *invoice->invoice_recurrence_basetime); else { json_add_string(js, "warning_invoice_missing_recurrence_basetime", "recurring invoices without a recurrence_basetime are invalid"); @@ -568,96 +609,34 @@ static void json_add_b12_invoice(struct json_stream *js, } } - if (invoice->payer_key) - json_add_pubkey(js, "payer_key", invoice->payer_key); - if (invoice->payer_info) - json_add_hex_talarr(js, "payer_info", invoice->payer_info); - if (invoice->payer_note) - json_add_stringn(js, "payer_note", invoice->payer_note, - tal_bytelen(invoice->payer_note)); - - /* BOLT-offers #12: - * - MUST reject the invoice if `created_at` is not present. - */ - if (invoice->created_at) { - json_add_u64(js, "created_at", *invoice->created_at); - } else { - json_add_string(js, "warning_invoice_missing_created_at", - "invoices without created_at are invalid"); - valid = false; - } - - /* BOLT-offers #12: - * - MUST reject the invoice if `payment_hash` is not present. - */ - if (invoice->payment_hash) - json_add_sha256(js, "payment_hash", invoice->payment_hash); - else { - json_add_string(js, "warning_invoice_missing_payment_hash", - "invoices without a payment_hash are invalid"); - valid = false; - } + if (invoice->invreq_metadata) + json_add_hex_talarr(js, "invreq_metadata", + invoice->invreq_metadata); + if (invoice->invreq_payer_note) + json_add_stringn(js, "payer_note", invoice->invreq_payer_note, + tal_bytelen(invoice->invreq_payer_note)); /* BOLT-offers #12: * - * - if the expiry for accepting payment is not 7200 seconds after - * `created_at`: - * - MUST set `relative_expiry` + * - if `invoice_relative_expiry` is present: + * - MUST reject the invoice if the current time since 1970-01-01 UTC + * 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 `invoice_created_at` plus 7200. */ - if (invoice->relative_expiry) - json_add_u32(js, "relative_expiry", *invoice->relative_expiry); + if (invoice->invoice_relative_expiry) + json_add_u32(js, "relative_expiry", *invoice->invoice_relative_expiry); else json_add_u32(js, "relative_expiry", 7200); - /* BOLT-offers #12: - * - if the `min_final_cltv_expiry` for the last HTLC in the route is - * not 18: - * - MUST set `min_final_cltv_expiry`. - */ - if (invoice->cltv) - json_add_u32(js, "min_final_cltv_expiry", *invoice->cltv); - else - json_add_u32(js, "min_final_cltv_expiry", 18); - - if (invoice->fallbacks) + if (invoice->invoice_fallbacks) valid &= json_add_fallbacks(js, - invoice->chain, - invoice->fallbacks); - - /* BOLT-offers #12: - * - if the offer contained `refund_for`: - * - MUST reject the invoice if `payer_key` does not match the invoice - * whose `payment_hash` is equal to `refund_for` - * `refunded_payment_hash` - * - MUST reject the invoice if `refund_signature` is not set. - * - MUST reject the invoice if `refund_signature` is not a valid - * signature using `payer_key` as described in - * [Signature Calculation](#signature-calculation). - */ - if (invoice->refund_signature) { - json_add_bip340sig(js, "refund_signature", - invoice->refund_signature); - if (!invoice->payer_key) { - json_add_string(js, "warning_invoice_refund_signature_missing_payer_key", - "Can't have refund_signature without payer key"); - valid = false; - } else if (!bolt12_check_signature(invoice->fields, - "invoice", - "refund_signature", - invoice->payer_key, - invoice->refund_signature)) { - json_add_string(js, "warning_invoice_refund_signature_invalid", - "refund_signature does not match"); - valid = false; - } - } else if (invoice->refund_for) { - json_add_string(js, "warning_invoice_refund_missing_signature", - "refund_for requires refund_signature"); - valid = false; - } + invoice->invreq_chain, + invoice->invoice_fallbacks); /* invoice_decode checked these */ - json_add_pubkey(js, "node_id", invoice->node_id); + json_add_pubkey(js, "node_id", invoice->offer_node_id); json_add_bip340sig(js, "signature", invoice->signature); json_add_bool(js, "valid", valid); @@ -668,8 +647,17 @@ static void json_add_invoice_request(struct json_stream *js, { bool valid = true; - if (invreq->chain) - json_add_sha256(js, "chain", &invreq->chain->shad.sha); + /* If there's an offer_node_id, then there's an offer. */ + if (invreq->offer_node_id) { + struct sha256 offer_id; + + invreq_offer_id(invreq, &offer_id); + json_add_sha256(js, "offer_id", &offer_id); + } + + /* FIXME-OFFERS: Rename all fields to invreq_ as per spec */ + if (invreq->invreq_chain) + json_add_sha256(js, "chain", &invreq->invreq_chain->shad.sha); /* BOLT-offers #12: * - MUST fail the request if `payer_key` is not present. @@ -677,50 +665,54 @@ static void json_add_invoice_request(struct json_stream *js, * - MUST fail the request if `features` contains unknown even bits. * - MUST fail the request if `offer_id` is not present. */ - if (invreq->offer_id) - json_add_sha256(js, "offer_id", invreq->offer_id); - else { - json_add_string(js, "warning_invoice_request_missing_offer_id", - "invoice_request requires offer_id"); - valid = false; - } - if (invreq->amount) + if (invreq->invreq_amount) json_add_amount_msat_only(js, "amount_msat", - amount_msat(*invreq->amount)); - if (invreq->features) - json_add_hex_talarr(js, "features", invreq->features); - if (invreq->quantity) - json_add_u64(js, "quantity", *invreq->quantity); + amount_msat(*invreq->invreq_amount)); + if (invreq->invreq_features) + json_add_hex_talarr(js, "features", invreq->invreq_features); + if (invreq->invreq_quantity) + json_add_u64(js, "quantity", *invreq->invreq_quantity); - if (invreq->recurrence_counter) + if (invreq->invreq_recurrence_counter) json_add_u32(js, "recurrence_counter", - *invreq->recurrence_counter); - if (invreq->recurrence_start) + *invreq->invreq_recurrence_counter); + if (invreq->invreq_recurrence_start) json_add_u32(js, "recurrence_start", - *invreq->recurrence_start); - if (invreq->payer_key) - json_add_pubkey(js, "payer_key", invreq->payer_key); + *invreq->invreq_recurrence_start); + /* BOLT-offers #12: + * - MUST fail the request if `invreq_payer_id` or `invreq_metadata` + * are not present. + */ + if (invreq->invreq_payer_id) + json_add_pubkey(js, "payer_key", invreq->invreq_payer_id); else { json_add_string(js, "warning_invoice_request_missing_payer_key", "invoice_request requires payer_key"); valid = false; } - if (invreq->payer_info) - json_add_hex_talarr(js, "payer_info", invreq->payer_info); - if (invreq->payer_note) - json_add_stringn(js, "payer_note", invreq->payer_note, - tal_bytelen(invreq->payer_note)); + if (invreq->invreq_metadata) + json_add_hex_talarr(js, "invreq_metadata", invreq->invreq_metadata); + else { + json_add_string(js, "warning_invoice_request_missing_invreq_metadata", + "invoice_request requires invreq_metadata"); + valid = false; + } + + if (invreq->invreq_payer_note) + json_add_stringn(js, "payer_note", invreq->invreq_payer_note, + tal_bytelen(invreq->invreq_payer_note)); /* BOLT-offers #12: - * - MUST fail the request if there is no `signature` field. - * - 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`. */ if (invreq->signature) { - if (invreq->payer_key + if (invreq->invreq_payer_id && !bolt12_check_signature(invreq->fields, "invoice_request", "signature", - invreq->payer_key, + invreq->invreq_payer_id, invreq->signature)) { json_add_string(js, "warning_invoice_request_invalid_signature", "Bad signature"); diff --git a/plugins/offers_invreq_hook.c b/plugins/offers_invreq_hook.c index 108764eb2eaa..fbd173de8ddf 100644 --- a/plugins/offers_invreq_hook.c +++ b/plugins/offers_invreq_hook.c @@ -22,6 +22,8 @@ struct invreq { /* The offer, once we've looked it up. */ struct tlv_offer *offer; + /* The offer id */ + struct sha256 offer_id; /* The invoice we're preparing (can require additional lookups) */ struct tlv_invoice *inv; @@ -40,14 +42,10 @@ fail_invreq_level(struct command *cmd, struct tlv_onionmsg_tlv *payload; struct tlv_invoice_error *err; - full_fmt = tal_fmt(tmpctx, "Failed invoice_request"); + full_fmt = tal_fmt(tmpctx, "Failed invreq"); if (invreq->invreq) { tal_append_fmt(&full_fmt, " %s", invrequest_encode(tmpctx, invreq->invreq)); - if (invreq->invreq->offer_id) - tal_append_fmt(&full_fmt, " for offer %s", - type_to_string(tmpctx, struct sha256, - invreq->invreq->offer_id)); } tal_append_fmt(&full_fmt, ": %s", fmt); @@ -124,13 +122,13 @@ test_field(struct command *cmd, */ static void set_recurring_inv_expiry(struct tlv_invoice *inv, u64 last_pay) { - inv->relative_expiry = tal(inv, u32); + inv->invoice_relative_expiry = tal(inv, u32); /* Don't give them a 0 second invoice, even if it's true. */ - if (last_pay <= *inv->created_at) - *inv->relative_expiry = 1; + if (last_pay <= *inv->invoice_created_at) + *inv->invoice_relative_expiry = 1; else - *inv->relative_expiry = last_pay - *inv->created_at; + *inv->invoice_relative_expiry = last_pay - *inv->invoice_created_at; /* FIXME: Shorten expiry if we're doing currency conversion! */ } @@ -221,9 +219,9 @@ static struct command_result *create_invoicereq(struct command *cmd, json_add_string(req->js, "invstring", invoice_encode(tmpctx, ir->inv)); json_add_preimage(req->js, "preimage", &ir->preimage); - json_add_label(req->js, ir->inv->offer_id, ir->inv->payer_key, - ir->inv->recurrence_counter - ? *ir->inv->recurrence_counter : 0); + json_add_label(req->js, &ir->offer_id, ir->inv->invreq_payer_id, + ir->inv->invreq_recurrence_counter + ? *ir->inv->invreq_recurrence_counter : 0); return send_outreq(cmd->plugin, req); } @@ -323,7 +321,8 @@ static struct command_result *listincoming_done(struct command *cmd, if (!feature_offered(features, OPT_ROUTE_BLINDING)) continue; - if (amount_msat_less(ci.htlc_max, amount_msat(*ir->inv->amount))) + if (amount_msat_less(ci.htlc_max, + amount_msat(*ir->inv->invoice_amount))) continue; /* Only pick a private one if no public candidates. */ @@ -346,7 +345,8 @@ static struct command_result *listincoming_done(struct command *cmd, /* 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))); + fmt_amount_msat(tmpctx, + amount_msat(*ir->inv->invoice_amount))); } else { struct privkey blinding; struct tlv_encrypted_data_tlv_payment_relay relay; @@ -358,9 +358,14 @@ static struct command_result *listincoming_done(struct command *cmd, relay.fee_base_msat = best->feebase; relay.fee_proportional_millionths = best->feeppm; + /* BOLT-offers #12: + * - if the expiry for accepting payment is not 7200 seconds + * after `invoice_created_at`: + * - MUST set `invoice_relative_expiry` + */ /* Give them 6 blocks, plus one per 10 minutes until expiry. */ - if (ir->inv->relative_expiry) - base = blockheight + 6 + *ir->inv->relative_expiry / 600; + if (ir->inv->invoice_relative_expiry) + base = blockheight + 6 + *ir->inv->invoice_relative_expiry / 600; else base = blockheight + 6 + 7200 / 600; constraints.max_cltv_expiry = base + best->cltv + cltv_final; @@ -368,14 +373,14 @@ static struct command_result *listincoming_done(struct command *cmd, 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; + ir->inv->invoice_paths = tal_arr(ir->inv, struct blinded_path *, 1); + ir->inv->invoice_paths[0] = tal(ir->inv->invoice_paths, struct blinded_path); + ir->inv->invoice_paths[0]->first_node_id = best->id; if (!pubkey_from_privkey(&blinding, - &ir->inv->paths[0]->blinding)) + &ir->inv->invoice_paths[0]->blinding)) abort(); - hops = tal_arr(ir->inv->paths[0], struct onionmsg_hop *, 2); - ir->inv->paths[0]->path = hops; + hops = tal_arr(ir->inv->invoice_paths[0], struct onionmsg_hop *, 2); + ir->inv->invoice_paths[0]->path = hops; /* First hop is the peer */ hops[0] = tal(hops, struct onionmsg_hop); @@ -395,18 +400,18 @@ static struct command_result *listincoming_done(struct command *cmd, &id, NULL, NULL, NULL, invoice_path_id(tmpctx, &invoicesecret_base, - ir->inv->payment_hash), + ir->inv->invoice_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; + ir->inv->invoice_blindedpay = tal_arr(ir->inv, struct blinded_payinfo *, 1); + ir->inv->invoice_blindedpay[0] = tal(ir->inv->invoice_blindedpay, struct blinded_payinfo); + ir->inv->invoice_blindedpay[0]->fee_base_msat = best->feebase; + ir->inv->invoice_blindedpay[0]->fee_proportional_millionths = best->feeppm; + ir->inv->invoice_blindedpay[0]->cltv_expiry_delta = best->cltv; + ir->inv->invoice_blindedpay[0]->htlc_minimum_msat = best->htlc_min; + ir->inv->invoice_blindedpay[0]->htlc_maximum_msat = best->htlc_max; + ir->inv->invoice_blindedpay[0]->features = NULL; } done: @@ -432,17 +437,17 @@ static struct command_result *check_period(struct command *cmd, struct command_result *err; /* If we have a recurrence base, that overrides. */ - if (ir->offer->recurrence_base) - basetime = ir->offer->recurrence_base->basetime; + if (ir->offer->offer_recurrence_base) + basetime = ir->offer->offer_recurrence_base->basetime; /* BOLT-offers-recurrence #12: * - if the invoice corresponds to an offer with `recurrence`: * - MUST set `recurrence_basetime` to the start of period #0 as * calculated by [Period Calculation](#offer-period-calculation). */ - ir->inv->recurrence_basetime = tal_dup(ir->inv, u64, &basetime); + ir->inv->invoice_recurrence_basetime = tal_dup(ir->inv, u64, &basetime); - period_idx = *ir->invreq->recurrence_counter; + period_idx = *ir->invreq->invreq_recurrence_counter; /* BOLT-offers-recurrence #12: * - if the offer had `recurrence_base` and `start_any_period` @@ -453,19 +458,19 @@ static struct command_result *check_period(struct command *cmd, * `recurrence_start` field plus the `recurrence_counter` * `counter` field. */ - if (ir->offer->recurrence_base - && ir->offer->recurrence_base->start_any_period) { - err = invreq_must_have(cmd, ir, recurrence_start); + if (ir->offer->offer_recurrence_base + && ir->offer->offer_recurrence_base->start_any_period) { + err = invreq_must_have(cmd, ir, invreq_recurrence_start); if (err) return err; - period_idx += *ir->invreq->recurrence_start; + period_idx += *ir->invreq->invreq_recurrence_start; /* BOLT-offers-recurrence #12: * - MUST set (or not set) `recurrence_start` exactly as the - * invoice_request did. + * invreq did. */ - ir->inv->recurrence_start - = tal_dup(ir->inv, u32, ir->invreq->recurrence_start); + ir->inv->invreq_recurrence_start + = tal_dup(ir->inv, u32, ir->invreq->invreq_recurrence_start); } else { /* BOLT-offers-recurrence #12: * @@ -475,7 +480,7 @@ static struct command_result *check_period(struct command *cmd, * - MUST consider the period index for this request to be the * `recurrence_counter` `counter` field. */ - err = invreq_must_not_have(cmd, ir, recurrence_start); + err = invreq_must_not_have(cmd, ir, invreq_recurrence_start); if (err) return err; } @@ -485,26 +490,26 @@ static struct command_result *check_period(struct command *cmd, * - MUST fail the request if the period index is greater than * `max_period`. */ - if (ir->offer->recurrence_limit - && period_idx > *ir->offer->recurrence_limit) { + if (ir->offer->offer_recurrence_limit + && period_idx > *ir->offer->offer_recurrence_limit) { return fail_invreq(cmd, ir, "period_index %"PRIu64" too great", period_idx); } - offer_period_paywindow(ir->offer->recurrence, - ir->offer->recurrence_paywindow, - ir->offer->recurrence_base, + offer_period_paywindow(ir->offer->offer_recurrence, + ir->offer->offer_recurrence_paywindow, + ir->offer->offer_recurrence_base, basetime, period_idx, &paywindow_start, &paywindow_end); - if (*ir->inv->created_at < paywindow_start) { + if (*ir->inv->invoice_created_at < paywindow_start) { return fail_invreq(cmd, ir, "period_index %"PRIu64 " too early (start %"PRIu64")", period_idx, paywindow_start); } - if (*ir->inv->created_at > paywindow_end) { + if (*ir->inv->invoice_created_at > paywindow_end) { return fail_invreq(cmd, ir, "period_index %"PRIu64 " too late (ended %"PRIu64")", @@ -524,21 +529,21 @@ static struct command_result *check_period(struct command *cmd, * - MUST adjust the *base invoice amount* proportional to time * remaining in the period. */ - if (*ir->invreq->recurrence_counter != 0 - && ir->offer->recurrence_paywindow - && ir->offer->recurrence_paywindow->proportional_amount == 1) { + if (*ir->invreq->invreq_recurrence_counter != 0 + && ir->offer->offer_recurrence_paywindow + && ir->offer->offer_recurrence_paywindow->proportional_amount == 1) { u64 start = offer_period_start(basetime, period_idx, - ir->offer->recurrence); + ir->offer->offer_recurrence); u64 end = offer_period_start(basetime, period_idx + 1, - ir->offer->recurrence); + ir->offer->offer_recurrence); - if (*ir->inv->created_at > start) { - *ir->inv->amount - *= (double)((*ir->inv->created_at - start) + if (*ir->inv->invoice_created_at > start) { + *ir->inv->invoice_amount + *= (double)((*ir->inv->invoice_created_at - start) / (end - start)); /* Round up to make it non-zero if necessary. */ - if (*ir->inv->amount == 0) - *ir->inv->amount = 1; + if (*ir->inv->invoice_amount == 0) + *ir->inv->invoice_amount = 1; } } @@ -559,7 +564,7 @@ static struct command_result *prev_invoice_done(struct command *cmd, if (arr->size == 0) { return fail_invreq(cmd, ir, "No previous invoice #%u", - *ir->inv->recurrence_counter - 1); + *ir->inv->invreq_recurrence_counter - 1); } /* Was it paid? */ @@ -567,7 +572,7 @@ static struct command_result *prev_invoice_done(struct command *cmd, if (!json_tok_streq(buf, status, "paid")) { return fail_invreq(cmd, ir, "Previous invoice #%u status %.*s", - *ir->inv->recurrence_counter - 1, + *ir->inv->invreq_recurrence_counter - 1, json_tok_full_len(status), json_tok_full(buf, status)); } @@ -577,7 +582,7 @@ static struct command_result *prev_invoice_done(struct command *cmd, if (!b12) { return fail_internalerr(cmd, ir, "Previous invoice #%u no bolt12 (%.*s)", - *ir->inv->recurrence_counter - 1, + *ir->inv->invreq_recurrence_counter - 1, json_tok_full_len(arr + 1), json_tok_full(buf, arr + 1)); } @@ -590,12 +595,12 @@ static struct command_result *prev_invoice_done(struct command *cmd, json_tok_full_len(b12), json_tok_full(buf, b12)); } - if (!previnv->recurrence_basetime) { + if (!previnv->invoice_recurrence_basetime) { return fail_internalerr(cmd, ir, "Previous invoice %.*s no recurrence_basetime?", json_tok_full_len(b12), json_tok_full(buf, b12)); } - return check_period(cmd, ir, *previnv->recurrence_basetime); + return check_period(cmd, ir, *previnv->invoice_recurrence_basetime); } /* Now, we need to check the previous invoice was paid, and maybe get timebase */ @@ -605,8 +610,8 @@ static struct command_result *check_previous_invoice(struct command *cmd, struct out_req *req; /* No previous? Just pass through */ - if (*ir->invreq->recurrence_counter == 0) - return check_period(cmd, ir, *ir->inv->created_at); + if (*ir->invreq->invreq_recurrence_counter == 0) + return check_period(cmd, ir, *ir->inv->invoice_created_at); req = jsonrpc_request_start(cmd->plugin, cmd, "listinvoices", @@ -614,9 +619,9 @@ static struct command_result *check_previous_invoice(struct command *cmd, error, ir); json_add_label(req->js, - ir->invreq->offer_id, - ir->invreq->payer_key, - *ir->invreq->recurrence_counter - 1); + &ir->offer_id, + ir->invreq->invreq_payer_id, + *ir->invreq->invreq_recurrence_counter - 1); return send_outreq(cmd->plugin, req); } @@ -639,24 +644,24 @@ static struct command_result *invreq_amount_by_quantity(struct command *cmd, const struct invreq *ir, u64 *raw_amt) { - assert(ir->offer->amount); + assert(ir->offer->offer_amount); /* BOLT-offers #12: * - MUST calculate the *base invoice amount* using the offer `amount`: */ - *raw_amt = *ir->offer->amount; + *raw_amt = *ir->offer->offer_amount; /* BOLT-offers #12: * - if request contains `quantity`, multiply by `quantity`. */ - if (ir->invreq->quantity) { - if (mul_overflows_u64(*ir->invreq->quantity, *raw_amt)) { + if (ir->invreq->invreq_quantity) { + if (mul_overflows_u64(*ir->invreq->invreq_quantity, *raw_amt)) { return fail_invreq(cmd, ir, "quantity %"PRIu64 " causes overflow", - *ir->invreq->quantity); + *ir->invreq->invreq_quantity); } - *raw_amt *= *ir->invreq->quantity; + *raw_amt *= *ir->invreq->invreq_quantity; } return NULL; @@ -669,28 +674,28 @@ static struct command_result *invreq_base_amount_simple(struct command *cmd, { struct command_result *err; - if (ir->offer->amount) { + if (ir->offer->offer_amount) { u64 raw_amount; - assert(!ir->offer->currency); + assert(!ir->offer->offer_currency); err = invreq_amount_by_quantity(cmd, ir, &raw_amount); if (err) return err; *amt = amount_msat(raw_amount); } else { - /* BOLT-offers-recurrence #12: + /* BOLT-offers #12: * - * - otherwise: - * - MUST fail the request if it does not contain `amount`. - * - MUST use the request `amount` as the *base invoice amount*. - * (Note: invoice amount can be further modified by recurrence - * below) + * The reader: + *... + * - otherwise (no `offer_amount`): + * - MUST fail the request if it does not contain + * `invreq_amount`. */ - err = invreq_must_have(cmd, ir, amount); + err = invreq_must_have(cmd, ir, invreq_amount); if (err) return err; - *amt = amount_msat(*ir->invreq->amount); + *amt = amount_msat(*ir->invreq->invreq_amount); } return NULL; } @@ -706,8 +711,8 @@ static struct command_result *handle_amount_and_recurrence(struct command *cmd, * - MUST fail the request if its `amount` is less than the * *base invoice amount*. */ - if (ir->offer->amount && ir->invreq->amount) { - if (amount_msat_less(amount_msat(*ir->invreq->amount), base_inv_amount)) { + if (ir->offer->offer_amount && ir->invreq->invreq_amount) { + if (amount_msat_less(amount_msat(*ir->invreq->invreq_amount), base_inv_amount)) { return fail_invreq(cmd, ir, "Amount must be at least %s", type_to_string(tmpctx, struct amount_msat, &base_inv_amount)); @@ -717,7 +722,7 @@ static struct command_result *handle_amount_and_recurrence(struct command *cmd, * the *base invoice amount*. */ /* Much == 5? Easier to divide and compare, than multiply. */ - if (amount_msat_greater(amount_msat_div(amount_msat(*ir->invreq->amount), 5), + if (amount_msat_greater(amount_msat_div(amount_msat(*ir->invreq->invreq_amount), 5), base_inv_amount)) { return fail_invreq(cmd, ir, "Amount vastly exceeds %s", type_to_string(tmpctx, struct amount_msat, @@ -727,22 +732,16 @@ static struct command_result *handle_amount_and_recurrence(struct command *cmd, * - MUST use the request's `amount` as the *base invoice * amount*. */ - base_inv_amount = amount_msat(*ir->invreq->amount); + base_inv_amount = amount_msat(*ir->invreq->invreq_amount); } /* This may be adjusted by recurrence if proportional_amount set */ - ir->inv->amount = tal_dup(ir->inv, u64, - &base_inv_amount.millisatoshis); /* Raw: wire protocol */ + ir->inv->invoice_amount = tal_dup(ir->inv, u64, + &base_inv_amount.millisatoshis); /* Raw: wire protocol */ /* Last of all, we handle recurrence details, which often requires * further lookups. */ - - /* BOLT-offers-recurrence #12: - * - MUST set (or not set) `recurrence_counter` exactly as the - * invoice_request did. - */ - if (ir->invreq->recurrence_counter) { - ir->inv->recurrence_counter = ir->invreq->recurrence_counter; + if (ir->inv->invreq_recurrence_counter) { return check_previous_invoice(cmd, ir); } /* We're happy with 2 hours timeout (default): they can always @@ -764,16 +763,16 @@ static struct command_result *currency_done(struct command *cmd, if (!msat) return fail_internalerr(cmd, ir, "Cannot convert currency %.*s: %.*s", - (int)tal_bytelen(ir->offer->currency), - (const char *)ir->offer->currency, + (int)tal_bytelen(ir->offer->offer_currency), + (const char *)ir->offer->offer_currency, json_tok_full_len(result), json_tok_full(buf, result)); if (!json_to_msat(buf, msat, &amount)) return fail_internalerr(cmd, ir, "Bad convert for currency %.*s: %.*s", - (int)tal_bytelen(ir->offer->currency), - (const char *)ir->offer->currency, + (int)tal_bytelen(ir->offer->offer_currency), + (const char *)ir->offer->offer_currency, json_tok_full_len(msat), json_tok_full(buf, msat)); @@ -789,7 +788,7 @@ static struct command_result *convert_currency(struct command *cmd, struct command_result *err; const struct iso4217_name_and_divisor *iso4217; - assert(ir->offer->currency); + assert(ir->offer->offer_currency); /* Multiply by quantity *first*, for best precision */ err = invreq_amount_by_quantity(cmd, ir, &raw_amount); @@ -802,14 +801,14 @@ static struct command_result *convert_currency(struct command *cmd, * - if offer `currency` is not the invoice currency, convert * to the invoice currency. */ - iso4217 = find_iso4217(ir->offer->currency, - tal_bytelen(ir->offer->currency)); + iso4217 = find_iso4217(ir->offer->offer_currency, + tal_bytelen(ir->offer->offer_currency)); /* We should not create offer with unknown currency! */ if (!iso4217) return fail_internalerr(cmd, ir, "Unknown offer currency %.*s", - (int)tal_bytelen(ir->offer->currency), - ir->offer->currency); + (int)tal_bytelen(ir->offer->offer_currency), + ir->offer->offer_currency); double_amount = (double)raw_amount; for (size_t i = 0; i < iso4217->minor_unit; i++) double_amount /= 10; @@ -817,8 +816,8 @@ static struct command_result *convert_currency(struct command *cmd, req = jsonrpc_request_start(cmd->plugin, cmd, "currencyconvert", currency_done, error, ir); json_add_stringn(req->js, "currency", - (const char *)ir->offer->currency, - tal_bytelen(ir->offer->currency)); + (const char *)ir->offer->offer_currency, + tal_bytelen(ir->offer->offer_currency)); json_add_primitive_fmt(req->js, "amount", "%f", double_amount); return send_outreq(cmd->plugin, req); } @@ -837,8 +836,8 @@ static struct command_result *listoffers_done(struct command *cmd, /* BOLT-offers #12: * - * - MUST fail the request if the `offer_id` does not refer to an - * unexpired offer. + * - MUST fail the request if the offer fields do not exactly match a + * valid, unexpired offer. */ if (arr->size == 0) return fail_invreq(cmd, ir, "Unknown offer"); @@ -863,6 +862,8 @@ static struct command_result *listoffers_done(struct command *cmd, json_tok_full_len(offertok), json_tok_full(buf, offertok)); } + + /* FIXME-OFFERS: we have these fields in invreq! */ ir->offer = offer_decode(ir, buf + b12tok->start, b12tok->end - b12tok->start, @@ -876,63 +877,65 @@ static struct command_result *listoffers_done(struct command *cmd, json_tok_full(buf, offertok)); } - if (ir->offer->absolute_expiry - && time_now().ts.tv_sec >= *ir->offer->absolute_expiry) { + if (ir->offer->offer_absolute_expiry + && time_now().ts.tv_sec >= *ir->offer->offer_absolute_expiry) { /* FIXME: do deloffer to disable it */ return fail_invreq(cmd, ir, "Offer expired"); } /* BOLT-offers #12: - * - if the offer had a `quantity_min` or `quantity_max` field: - * - MUST fail the request if there is no `quantity` field. - * - MUST fail the request if there is `quantity` is not within - * that (inclusive) range. + * - if `offer_quantity_max` is present: + * - MUST fail the request if there is no `invreq_quantity` field. + * - if `offer_quantity_max` is non-zero: + * - MUST fail the request if `invreq_quantity` is zero, OR greater than + * `offer_quantity_max`. * - otherwise: - * - MUST fail the request if there is a `quantity` field. + * - MUST fail the request if there is an `invreq_quantity` field. */ - if (ir->offer->quantity_min || ir->offer->quantity_max) { - err = invreq_must_have(cmd, ir, quantity); + if (ir->offer->offer_quantity_max) { + err = invreq_must_have(cmd, ir, invreq_quantity); if (err) return err; - if (ir->offer->quantity_min && - *ir->invreq->quantity < *ir->offer->quantity_min) { + if (*ir->invreq->invreq_quantity == 0) return fail_invreq(cmd, ir, - "quantity %"PRIu64 " < %"PRIu64, - *ir->invreq->quantity, - *ir->offer->quantity_min); - } + "quantity zero invalid"); - if (ir->offer->quantity_max && - *ir->invreq->quantity > *ir->offer->quantity_max) { + if (*ir->offer->offer_quantity_max && + *ir->invreq->invreq_quantity > *ir->offer->offer_quantity_max) { return fail_invreq(cmd, ir, "quantity %"PRIu64" > %"PRIu64, - *ir->invreq->quantity, - *ir->offer->quantity_max); + *ir->invreq->invreq_quantity, + *ir->offer->offer_quantity_max); } } else { - err = invreq_must_not_have(cmd, ir, quantity); + err = invreq_must_not_have(cmd, ir, invreq_quantity); if (err) return err; } + /* BOLT-offers #12: + * - MUST fail the request if `invreq_signature` is not correct as + * detailed in [Signature Calculation](#signature-calculation) using + * the `invreq_payer_id`. + */ err = invreq_must_have(cmd, ir, signature); if (err) return err; if (!check_payer_sig(cmd, ir->invreq, - ir->invreq->payer_key, + ir->invreq->invreq_payer_id, ir->invreq->signature)) { return fail_invreq(cmd, ir, "bad signature"); } - if (ir->offer->recurrence) { + if (ir->offer->offer_recurrence) { /* BOLT-offers-recurrence #12: * * - if the offer had a `recurrence`: * - MUST fail the request if there is no `recurrence_counter` * field. */ - err = invreq_must_have(cmd, ir, recurrence_counter); + err = invreq_must_have(cmd, ir, invreq_recurrence_counter); if (err) return err; } else { @@ -943,78 +946,54 @@ static struct command_result *listoffers_done(struct command *cmd, * - MUST fail the request if there is a `recurrence_start` * field. */ - err = invreq_must_not_have(cmd, ir, recurrence_counter); + err = invreq_must_not_have(cmd, ir, invreq_recurrence_counter); if (err) return err; - err = invreq_must_not_have(cmd, ir, recurrence_start); + err = invreq_must_not_have(cmd, ir, invreq_recurrence_start); if (err) return err; } - ir->inv = tlv_invoice_new(cmd); /* BOLT-offers #12: - * - if the chain for the invoice is not solely bitcoin: - * - MUST specify `chains` the offer is valid for. + * The writer: + * - MUST copy all non-signature fields from the invreq (including + * unknown fields). */ - if (!streq(chainparams->network_name, "bitcoin")) { - ir->inv->chain = tal_dup(ir->inv, struct bitcoin_blkid, - &chainparams->genesis_blockhash); - } + ir->inv = invoice_for_invreq(cmd, ir->invreq); + assert(ir->inv->invreq_payer_id); /* BOLT-offers #12: - * - MUST set `offer_id` to the id of the offer. - */ - /* Which is the same as the invreq */ - ir->inv->offer_id = tal_dup(ir->inv, struct sha256, - ir->invreq->offer_id); - ir->inv->description = tal_dup_talarr(ir->inv, char, - ir->offer->description); - ir->inv->features = tal_dup_talarr(ir->inv, u8, - plugin_feature_set(cmd->plugin) - ->bits[BOLT11_FEATURE]); - /* FIXME: Insert paths and payinfo */ - - ir->inv->issuer = tal_dup_talarr(ir->inv, char, ir->offer->issuer); - ir->inv->node_id = tal_dup(ir->inv, struct pubkey, ir->offer->node_id); - /* BOLT-offers #12: - * - MUST set (or not set) `quantity` exactly as the invoice_request - * did. + * - if `offer_node_id` is present: + * - MUST set `invoice_node_id` to `offer_node_id`. */ - if (ir->offer->quantity_min || ir->offer->quantity_max) - ir->inv->quantity = tal_dup(ir->inv, u64, ir->invreq->quantity); + /* We always provide an offer_node_id! */ + ir->inv->invoice_node_id = ir->inv->offer_node_id; /* BOLT-offers #12: - * - MUST set `payer_key` exactly as the invoice_request did. + * - MUST set `invoice_created_at` to the number of seconds since + * Midnight 1 January 1970, UTC when the offer was created. */ - ir->inv->payer_key = tal_dup(ir->inv, struct pubkey, - ir->invreq->payer_key); + ir->inv->invoice_created_at = tal(ir->inv, u64); + *ir->inv->invoice_created_at = time_now().ts.tv_sec; /* BOLT-offers #12: - * - MUST set (or not set) `payer_info` exactly as the invoice_request - * did. + * - MUST set `invoice_payment_hash` to the SHA256 hash of the + * `payment_preimage` that will be given in return for payment. */ - ir->inv->payer_info - = tal_dup_talarr(ir->inv, u8, ir->invreq->payer_info); + randombytes_buf(&ir->preimage, sizeof(ir->preimage)); + ir->inv->invoice_payment_hash = tal(ir->inv, struct sha256); + sha256(ir->inv->invoice_payment_hash, + &ir->preimage, sizeof(ir->preimage)); /* BOLT-offers #12: - * - MUST set (or not set) `payer_note` exactly as the invoice_request - * did, or MUST not set it. + * - or if it allows multiple parts to pay the invoice: + * - MUST set `invoice_features`.`features` bit `MPP/optional` */ - /* i.e. we don't have to do anything, but we do. */ - ir->inv->payer_note - = tal_dup_talarr(ir->inv, char, ir->invreq->payer_note); - - randombytes_buf(&ir->preimage, sizeof(ir->preimage)); - ir->inv->payment_hash = tal(ir->inv, struct sha256); - sha256(ir->inv->payment_hash, &ir->preimage, sizeof(ir->preimage)); - - ir->inv->cltv = tal_dup(ir->inv, u16, &cltv_final); - - ir->inv->created_at = tal(ir->inv, u64); - *ir->inv->created_at = time_now().ts.tv_sec; + ir->inv->invoice_features + = plugin_feature_set(cmd->plugin)->bits[BOLT12_INVOICE_FEATURE]; /* We may require currency lookup; if so, do it now. */ - if (ir->offer->amount && ir->offer->currency) + if (ir->offer->offer_amount && ir->offer->offer_currency) return convert_currency(cmd, ir); err = invreq_base_amount_simple(cmd, ir, &amt); @@ -1023,25 +1002,19 @@ static struct command_result *listoffers_done(struct command *cmd, return handle_amount_and_recurrence(cmd, ir, amt); } -static struct command_result *handle_offerless_request(struct command *cmd, - struct invreq *ir) -{ - /* FIXME: shut up and take their money! */ - return fail_internalerr(cmd, ir, "FIXME: handle offerless req!"); -} - struct command_result *handle_invoice_request(struct command *cmd, const u8 *invreqbin, struct blinded_path *reply_path) { size_t len = tal_count(invreqbin); + const u8 *cursor = invreqbin; struct invreq *ir = tal(cmd, struct invreq); struct out_req *req; int bad_feature; ir->reply_path = tal_steal(ir, reply_path); - ir->invreq = fromwire_tlv_invoice_request(cmd, &invreqbin, &len); + ir->invreq = fromwire_tlv_invoice_request(cmd, &cursor, &len); if (!ir->invreq) { return fail_invreq(cmd, ir, "Invalid invreq %s", @@ -1050,13 +1023,39 @@ struct command_result *handle_invoice_request(struct command *cmd, /* BOLT-offers #12: * - * The reader of an invoice_request: + * The reader: + * - MUST fail the request if `invreq_payer_id` or `invreq_metadata` + * are not present. + */ + if (!ir->invreq->invreq_payer_id) + return fail_invreq(cmd, ir, "Missing invreq_payer_id"); + if (!ir->invreq->invreq_metadata) + return fail_invreq(cmd, ir, "Missing invreq_metadata"); + + /* BOLT-offers #12: + * The reader: + * ... + * - MUST fail the request if any non-signature TLV fields greater or + * equal to 160. + */ + /* BOLT-offers #12: + * Each form is signed using one or more *signature TLV elements*: + * TLV types 240 through 1000 (inclusive) + */ + if (tlv_span(invreqbin, 0, 159, NULL) + + tlv_span(invreqbin, 240, 1000, NULL) != tal_bytelen(invreqbin)) + return fail_invreq(cmd, ir, "Fields beyond 160"); + + /* BOLT-offers #12: + * + * The reader of an invreq: *... - * - MUST fail the request if `features` contains unknown even bits. + * - if `invreq_features` contains unknown _even_ bits that are non-zero: + * - MUST fail the request. */ bad_feature = features_unsupported(plugin_feature_set(cmd->plugin), - ir->invreq->features, - BOLT11_FEATURE); + ir->invreq->invreq_features, + BOLT12_INVREQ_FEATURE); if (bad_feature != -1) { return fail_invreq(cmd, ir, "Unsupported invreq feature %i", @@ -1065,34 +1064,41 @@ struct command_result *handle_invoice_request(struct command *cmd, /* BOLT-offers #12: * - * The reader of an invoice_request: + * The reader: *... - * - 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 `invreq_chain` is not present: + * - MUST fail the request if bitcoin is not a supported chain. + * - otherwise: + * - MUST fail the request if `invreq_chain`.`chain` is not a + * supported chain. */ - if (!bolt12_chain_matches(ir->invreq->chain, chainparams)) { + if (!bolt12_chain_matches(ir->invreq->invreq_chain, chainparams)) { return fail_invreq(cmd, ir, "Wrong chain %s", - tal_hex(tmpctx, ir->invreq->chain)); + tal_hex(tmpctx, ir->invreq->invreq_chain)); } /* BOLT-offers #12: * - * The reader of an invoice_request: - * - MUST fail the request if `payer_key` is not present. + * - otherwise (no `offer_node_id`, not a response to our offer): */ - if (!ir->invreq->payer_key) - return fail_invreq(cmd, ir, "Missing payer key"); + /* FIXME-OFFERS: handle this! */ + if (!ir->invreq->offer_node_id) { + return fail_invreq(cmd, ir, "Not based on an offer"); + } - if (!ir->invreq->offer_id) - return handle_offerless_request(cmd, ir); + /* BOLT-offers #12: + * + * - if `offer_node_id` is present (response to an offer): + * - MUST fail the request if the offer fields do not exactly match a + * valid, unexpired offer. + */ + invreq_offer_id(ir->invreq, &ir->offer_id); /* Now, look up offer */ req = jsonrpc_request_start(cmd->plugin, cmd, "listoffers", listoffers_done, error, ir); - json_add_sha256(req->js, "offer_id", ir->invreq->offer_id); + json_add_sha256(req->js, "offer_id", &ir->offer_id); return send_outreq(cmd->plugin, req); } diff --git a/plugins/offers_offer.c b/plugins/offers_offer.c index 55a9c035629c..5c2302035503 100644 --- a/plugins/offers_offer.c +++ b/plugins/offers_offer.c @@ -21,7 +21,7 @@ static bool msat_or_any(const char *buffer, buffer + tok->start, tok->end - tok->start)) return false; - offer->amount = tal_dup(offer, u64, + offer->offer_amount = tal_dup(offer, u64, &msat.millisatoshis); /* Raw: other currencies */ return true; } @@ -39,7 +39,7 @@ static struct command_result *param_amount(struct command *cmd, if (msat_or_any(buffer, tok, offer)) return NULL; - offer->amount = tal(offer, u64); + offer->offer_amount = tal(offer, u64); /* BOLT-offers #12: * @@ -58,7 +58,7 @@ static struct command_result *param_amount(struct command *cmd, ISO4217_NAMELEN, buffer + tok->end - ISO4217_NAMELEN); - offer->currency + offer->offer_currency = tal_dup_arr(offer, utf8, isocode->name, ISO4217_NAMELEN, 0); number = *tok; @@ -77,19 +77,19 @@ static struct command_result *param_amount(struct command *cmd, "Bad minor units"); } - if (!json_to_u64(buffer, &whole, offer->amount)) + if (!json_to_u64(buffer, &whole, offer->offer_amount)) return command_fail_badparam(cmd, name, buffer, tok, "should be 'any', msatoshis or [.]"); for (size_t i = 0; i < isocode->minor_unit; i++) { - if (mul_overflows_u64(*offer->amount, 10)) + if (mul_overflows_u64(*offer->offer_amount, 10)) return command_fail_badparam(cmd, name, buffer, &whole, "excessively large value"); - *offer->amount *= 10; + *offer->offer_amount *= 10; } - *offer->amount += cents; + *offer->offer_amount += cents; return NULL; } @@ -139,8 +139,7 @@ static struct command_result *param_recurrence(struct command *cmd, const char *name, const char *buffer, const jsmntok_t *tok, - struct tlv_offer_recurrence - **recurrence) + struct recurrence **recurrence) { u32 mul; const struct time_string *ts; @@ -150,7 +149,7 @@ static struct command_result *param_recurrence(struct command *cmd, return command_fail_badparam(cmd, name, buffer, tok, "not a valid time"); - *recurrence = tal(cmd, struct tlv_offer_recurrence); + *recurrence = tal(cmd, struct recurrence); (*recurrence)->time_unit = ts->unit; (*recurrence)->period = ts->mul * mul; return NULL; @@ -160,12 +159,12 @@ static struct command_result *param_recurrence_base(struct command *cmd, const char *name, const char *buffer, const jsmntok_t *tok, - struct tlv_offer_recurrence_base **base) + struct recurrence_base **base) { /* Make copy so we can manipulate it */ jsmntok_t t = *tok; - *base = tal(cmd, struct tlv_offer_recurrence_base); + *base = tal(cmd, struct recurrence_base); if (json_tok_startswith(buffer, &t, "@")) { t.start++; (*base)->start_any_period = false; @@ -183,12 +182,12 @@ static struct command_result *param_recurrence_paywindow(struct command *cmd, const char *name, const char *buffer, const jsmntok_t *tok, - struct tlv_offer_recurrence_paywindow + struct recurrence_paywindow **paywindow) { jsmntok_t t, before, after; - *paywindow = tal(cmd, struct tlv_offer_recurrence_paywindow); + *paywindow = tal(cmd, struct recurrence_paywindow); t = *tok; if (json_tok_endswith(buffer, &t, "%")) { (*paywindow)->proportional_amount = true; @@ -231,7 +230,7 @@ static struct command_result *check_result(struct command *cmd, &active)) { return command_fail(cmd, LIGHTNINGD, - "Bad creaoffer status reply %.*s", + "Bad createoffer status reply %.*s", json_tok_full_len(result), json_tok_full(buf, result)); } @@ -289,19 +288,18 @@ struct command_result *json_offer(struct command *cmd, p_req("description", param_escaped_string, &desc), p_opt("issuer", param_escaped_string, &issuer), p_opt("label", param_escaped_string, &offinfo->label), - p_opt("quantity_min", param_u64, &offer->quantity_min), - p_opt("quantity_max", param_u64, &offer->quantity_max), - p_opt("absolute_expiry", param_u64, &offer->absolute_expiry), - p_opt("recurrence", param_recurrence, &offer->recurrence), + p_opt("quantity_max", param_u64, &offer->offer_quantity_max), + p_opt("absolute_expiry", param_u64, &offer->offer_absolute_expiry), + p_opt("recurrence", param_recurrence, &offer->offer_recurrence), p_opt("recurrence_base", param_recurrence_base, - &offer->recurrence_base), + &offer->offer_recurrence_base), p_opt("recurrence_paywindow", param_recurrence_paywindow, - &offer->recurrence_paywindow), + &offer->offer_recurrence_paywindow), p_opt("recurrence_limit", param_number, - &offer->recurrence_limit), + &offer->offer_recurrence_limit), p_opt_def("single_use", param_bool, &offinfo->single_use, false), /* FIXME: hints support! */ @@ -312,66 +310,66 @@ struct command_result *json_offer(struct command *cmd, return command_fail(cmd, LIGHTNINGD, "experimental-offers not enabled"); - /* BOLT-offers #12: - * - MUST NOT set `quantity_min` or `quantity_max` less than 1. - */ - if (offer->quantity_min && *offer->quantity_min < 1) - return command_fail_badparam(cmd, "quantity_min", - buffer, params, - "must be >= 1"); - if (offer->quantity_max && *offer->quantity_max < 1) + /* Doesn't make sense to have max quantity 1. */ + if (offer->offer_quantity_max && *offer->offer_quantity_max == 1) return command_fail_badparam(cmd, "quantity_max", buffer, params, - "must be >= 1"); - /* BOLT-offers #12: - * - if both: - * - MUST set `quantity_min` less than or equal to `quantity_max`. - */ - if (offer->quantity_min && offer->quantity_max) { - if (*offer->quantity_min > *offer->quantity_max) - return command_fail_badparam(cmd, "quantity_min", - buffer, params, - "must be <= quantity_max"); - } - + "must be 0 or > 1"); /* 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. */ if (!streq(chainparams->network_name, "bitcoin")) { - offer->chains = tal_arr(offer, struct bitcoin_blkid, 1); - offer->chains[0] = chainparams->genesis_blockhash; + offer->offer_chains = tal_arr(offer, struct bitcoin_blkid, 1); + offer->offer_chains[0] = chainparams->genesis_blockhash; } - if (!offer->recurrence) { - if (offer->recurrence_limit) + if (!offer->offer_recurrence) { + if (offer->offer_recurrence_limit) return command_fail_badparam(cmd, "recurrence_limit", buffer, params, "needs recurrence"); - if (offer->recurrence_base) + if (offer->offer_recurrence_base) return command_fail_badparam(cmd, "recurrence_base", buffer, params, "needs recurrence"); - if (offer->recurrence_paywindow) + if (offer->offer_recurrence_paywindow) return command_fail_badparam(cmd, "recurrence_paywindow", buffer, params, "needs recurrence"); } - offer->description = tal_dup_arr(offer, char, desc, strlen(desc), 0); + /* BOLT-offers #12: + * - MUST set `offer_description` to a complete description of the + * purpose of the payment. + */ + offer->offer_description + = tal_dup_arr(offer, char, desc, strlen(desc), 0); + + /* BOLT-offers #12: + * - if it sets `offer_issuer`: + * - SHOULD set it to identify the issuer of the invoice clearly. + * - if it includes a domain name: + * - SHOULD begin it with either user@domain or domain + * - MAY follow with a space and more text + */ if (issuer) { - offer->issuer + offer->offer_issuer = tal_dup_arr(offer, char, issuer, strlen(issuer), 0); } - offer->node_id = tal_dup(offer, struct pubkey, &id); + /* BOLT-offers #12: + * - MUST set `offer_node_id` to the node's public key to request the + * invoice from. + */ + offer->offer_node_id = tal_dup(offer, struct pubkey, &id); /* If they specify a different currency, warn if we can't * convert it! */ - if (offer->currency) { + if (offer->offer_currency) { struct out_req *req; req = jsonrpc_request_start(cmd->plugin, cmd, "currencyconvert", @@ -379,8 +377,8 @@ struct command_result *json_offer(struct command *cmd, offinfo); json_add_u32(req->js, "amount", 1); json_add_stringn(req->js, "currency", - (const char *)offer->currency, - tal_bytelen(offer->currency)); + (const char *)offer->offer_currency, + tal_bytelen(offer->offer_currency)); return send_outreq(cmd->plugin, req); } diff --git a/plugins/pay.c b/plugins/pay.c index a5149acf9bb3..2f29d2940a55 100644 --- a/plugins/pay.c +++ b/plugins/pay.c @@ -1092,83 +1092,73 @@ static struct command_result *json_pay(struct command *cmd, /* p->features = tal_steal(p, b12->features); */ p->features = NULL; - if (!b12->node_id) + if (!b12->invoice_node_id) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "invoice missing node_id"); - if (!b12->payment_hash) + if (!b12->invoice_payment_hash) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "invoice missing payment_hash"); - if (!b12->created_at) + if (!b12->invoice_created_at) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "invoice missing created_at"); - if (b12->amount) { - invmsat = tal(cmd, struct amount_msat); - *invmsat = amount_msat(*b12->amount); - } else - invmsat = NULL; + if (!b12->invoice_amount) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "invoice missing invoice_amount"); + invmsat = tal(cmd, struct amount_msat); + *invmsat = amount_msat(*b12->invoice_amount); p->destination = tal(p, struct node_id); - node_id_from_pubkey(p->destination, b12->node_id); - p->payment_hash = tal_dup(p, struct sha256, b12->payment_hash); - if (b12->recurrence_counter && !label) + node_id_from_pubkey(p->destination, b12->invoice_node_id); + p->payment_hash = tal_dup(p, struct sha256, + b12->invoice_payment_hash); + if (b12->invreq_recurrence_counter && !label) return command_fail( cmd, JSONRPC2_INVALID_PARAMS, "recurring invoice requires a label"); /* BOLT-offers #12: - * - MUST reject the invoice if `blindedpay` is not present. + * - MUST reject the invoice if `invoice_paths` is not present + * or is empty. */ - /* 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]); + if (tal_count(b12->invoice_paths) == 0) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "invoice missing invoice_paths"); - /* 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); - merkle_tlv(b12->fields, &merkle); - memcpy(p->payment_secret, &merkle, sizeof(merkle)); - BUILD_ASSERT(sizeof(*p->payment_secret) == - sizeof(merkle)); + /* BOLT-offers #12: + * - MUST reject the invoice if `invoice_blindedpay` does not + * contain exactly one `blinded_payinfo` per + * `invoice_paths`.`blinded_path`. */ + if (tal_count(b12->invoice_paths) + != tal_count(b12->invoice_blindedpay)) { + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Wrong blinding info: %zu paths, %zu payinfo", + tal_count(b12->invoice_paths), + tal_count(b12->invoice_blindedpay)); } + + /* FIXME: do MPP across these! We choose first one. */ + p->blindedpath = tal_steal(p, b12->invoice_paths[0]); + p->blindedpay = tal_steal(p, b12->invoice_blindedpay[0]); + p->min_final_cltv_expiry = p->blindedpay->cltv_expiry_delta; + + /* Set destination to introduction point */ + node_id_from_pubkey(p->destination, &p->blindedpath->first_node_id); p->payment_metadata = NULL; p->routes = NULL; - if (b12->cltv) - p->min_final_cltv_expiry = *b12->cltv; - else - p->min_final_cltv_expiry = 18; /* 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 + * 1970-01-01 UTC 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. + * 1970-01-01 UTC is greater than `invoice_created_at` plus + * 7200. */ - if (b12->relative_expiry) - invexpiry = *b12->created_at + *b12->relative_expiry; + if (b12->invoice_relative_expiry) + invexpiry = *b12->invoice_created_at + *b12->invoice_relative_expiry; else - invexpiry = *b12->created_at + BOLT12_DEFAULT_REL_EXPIRY; + invexpiry = *b12->invoice_created_at + BOLT12_DEFAULT_REL_EXPIRY; p->local_offer_id = tal_steal(p, local_offer_id); } diff --git a/tests/test_pay.py b/tests/test_pay.py index 5cd79830f868..93c1f07e92a0 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -4383,7 +4383,7 @@ def test_offer_needs_option(node_factory): l1.rpc.call('fetchinvoice', {'offer': 'aaaa'}) # Decode still works though - assert l1.rpc.decode('lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcgqyys5qq7yypxdeze35wncs2l2u4gfzyrpds00e6yakfrt6ctrw5n9qanzhqr2x8sgp3lqxpxd82j87j67wyff9cd9msgagq8hveftdkx5t3e98gj2x7ac99hhwlpj9yvj79yz3l8gdlmdmhq47ct9pkedfd8naksd8f8gpar')['valid'] + assert l1.rpc.decode('lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcgqyqs5pr5v4ehg93pqfnwgkvdr57yzh6h92zg3qctvrm7w38djg67kzcm4yeg8vc4cq63s')['valid'] def test_offer(node_factory, bitcoind): @@ -4449,14 +4449,14 @@ def test_offer(node_factory, bitcoind): offer['bolt12']]).decode('UTF-8') assert 'issuer: ' + weird_issuer in output - # Test quantity min/max + # Test quantity ret = l1.rpc.call('offer', {'amount': '100000sat', - 'description': 'quantity_min test', - 'quantity_min': 1}) + 'description': 'quantity_max existence test', + 'quantity_max': 0}) offer = only_one(l1.rpc.call('listoffers', [ret['offer_id']])['offers']) output = subprocess.check_output([bolt12tool, 'decode', offer['bolt12']]).decode('UTF-8') - assert 'quantity_min: 1' in output + assert 'quantity_max: 0' in output ret = l1.rpc.call('offer', {'amount': '100000sat', 'description': 'quantity_max test', @@ -4466,26 +4466,6 @@ def test_offer(node_factory, bitcoind): offer['bolt12']]).decode('UTF-8') assert 'quantity_max: 2' in output - # BOLT-offers #12: - # * - MUST NOT set `quantity_min` or `quantity_max` less than 1. - with pytest.raises(RpcError, match='quantity_min: must be >= 1'): - ret = l1.rpc.call('offer', {'amount': '100000sat', - 'description': 'quantity_min test', - 'quantity_min': 0}) - - with pytest.raises(RpcError, match='quantity_max: must be >= 1'): - ret = l1.rpc.call('offer', {'amount': '100000sat', - 'description': 'quantity_max test', - 'quantity_max': 0}) - # BOLT-offers #12: - # - if both: - # - MUST set `quantity_min` greater or equal to `quantity_max`. - with pytest.raises(RpcError, match='quantity_min: must be <= quantity_max'): - ret = l1.rpc.call('offer', {'amount': '100000sat', - 'description': 'quantity_max test', - 'quantity_min': 10, - 'quantity_max': 9}) - # Test absolute_expiry exp = int(time.time() + 2) ret = l1.rpc.call('offer', {'amount': '100000sat', @@ -5241,5 +5221,5 @@ def test_payerkey(node_factory): "03a3bbda0137722ba62207b9d3e5e6cc2a11e58480f801892093e01383aacb7fb2"] for n, k in zip(nodes, expected_keys): - b12 = n.rpc.createinvoicerequest('lnr1qvsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcyyrjjthf4rh99n7equvlrzrlalcacxj4y9hgzxc79yrntrth6mp3nkvssy5mac4pkfq2m3gq4ttajwh097s')['bolt12'] + b12 = n.rpc.createinvoicerequest('lnr1qqgz2d7u2smys9dc5q2447e8thjlgq3qqc3xu3s3rg94nj40zfsy866mhu5vxne6tcej5878k2mneuvgjy8ssqvepgz5zsjrg3z3vggzvkm2khkgvrxj27r96c00pwl4kveecdktm29jdd6w0uwu5jgtv5v9qgqxyfhyvyg6pdvu4tcjvpp7kkal9rp57wj7xv4pl3ajku70rzy3pu')['bolt12'] assert n.rpc.decode(b12)['payer_key'] == k diff --git a/wire/bolt12_exp_wire.csv b/wire/bolt12_exp_wire.csv index 8c20ced38ef9..10ba1faf0b1c 100644 --- a/wire/bolt12_exp_wire.csv +++ b/wire/bolt12_exp_wire.csv @@ -1,119 +1,163 @@ -tlvtype,offer,chains,2 -tlvdata,offer,chains,chains,chain_hash,... -tlvtype,offer,currency,6 -tlvdata,offer,currency,iso4217,utf8,... -tlvtype,offer,amount,8 -tlvdata,offer,amount,amount,tu64, -tlvtype,offer,description,10 -tlvdata,offer,description,description,utf8,... -tlvtype,offer,features,12 -tlvdata,offer,features,features,byte,... -tlvtype,offer,absolute_expiry,14 -tlvdata,offer,absolute_expiry,seconds_from_epoch,tu64, -tlvtype,offer,paths,16 -tlvdata,offer,paths,paths,blinded_path,... -tlvtype,offer,issuer,20 -tlvdata,offer,issuer,issuer,utf8,... -tlvtype,offer,quantity_min,22 -tlvdata,offer,quantity_min,min,tu64, -tlvtype,offer,quantity_max,24 -tlvdata,offer,quantity_max,max,tu64, -tlvtype,offer,recurrence,26 -tlvdata,offer,recurrence,time_unit,byte, -tlvdata,offer,recurrence,period,tu32, -tlvtype,offer,recurrence_paywindow,64 -tlvdata,offer,recurrence_paywindow,seconds_before,u32, -tlvdata,offer,recurrence_paywindow,proportional_amount,byte, -tlvdata,offer,recurrence_paywindow,seconds_after,tu32, -tlvtype,offer,recurrence_limit,66 -tlvdata,offer,recurrence_limit,max_period,tu32, -tlvtype,offer,recurrence_base,28 -tlvdata,offer,recurrence_base,start_any_period,byte, -tlvdata,offer,recurrence_base,basetime,tu64, -tlvtype,offer,node_id,30 -tlvdata,offer,node_id,node_id,point, -tlvtype,offer,send_invoice,54 -tlvtype,offer,refund_for,34 -tlvdata,offer,refund_for,refunded_payment_hash,sha256, -tlvtype,offer,signature,240 -tlvdata,offer,signature,sig,bip340sig, -tlvtype,invoice_request,chain,3 -tlvdata,invoice_request,chain,chain,chain_hash, -tlvtype,invoice_request,offer_id,4 -tlvdata,invoice_request,offer_id,offer_id,sha256, -tlvtype,invoice_request,amount,8 -tlvdata,invoice_request,amount,msat,tu64, -tlvtype,invoice_request,features,12 -tlvdata,invoice_request,features,features,byte,... -tlvtype,invoice_request,quantity,32 -tlvdata,invoice_request,quantity,quantity,tu64, -tlvtype,invoice_request,recurrence_counter,36 -tlvdata,invoice_request,recurrence_counter,counter,tu32, -tlvtype,invoice_request,recurrence_start,68 -tlvdata,invoice_request,recurrence_start,period_offset,tu32, -tlvtype,invoice_request,payer_key,38 -tlvdata,invoice_request,payer_key,key,point, -tlvtype,invoice_request,payer_note,39 -tlvdata,invoice_request,payer_note,note,utf8,... -tlvtype,invoice_request,payer_info,50 -tlvdata,invoice_request,payer_info,blob,byte,... -tlvtype,invoice_request,replace_invoice,56 -tlvdata,invoice_request,replace_invoice,payment_hash,sha256, +tlvtype,offer,offer_chains,2 +tlvdata,offer,offer_chains,chains,chain_hash,... +tlvtype,offer,offer_metadata,4 +tlvdata,offer,offer_metadata,data,byte,... +tlvtype,offer,offer_currency,6 +tlvdata,offer,offer_currency,iso4217,utf8,... +tlvtype,offer,offer_amount,8 +tlvdata,offer,offer_amount,amount,tu64, +tlvtype,offer,offer_description,10 +tlvdata,offer,offer_description,description,utf8,... +tlvtype,offer,offer_features,12 +tlvdata,offer,offer_features,features,byte,... +tlvtype,offer,offer_absolute_expiry,14 +tlvdata,offer,offer_absolute_expiry,seconds_from_epoch,tu64, +tlvtype,offer,offer_paths,16 +tlvdata,offer,offer_paths,paths,blinded_path,... +tlvtype,offer,offer_issuer,18 +tlvdata,offer,offer_issuer,issuer,utf8,... +tlvtype,offer,offer_quantity_max,20 +tlvdata,offer,offer_quantity_max,max,tu64, +tlvtype,offer,offer_node_id,22 +tlvdata,offer,offer_node_id,node_id,point, +tlvtype,offer,offer_recurrence,26 +tlvdata,offer,offer_recurrence,recurrence,recurrence, +tlvtype,offer,offer_recurrence_paywindow,28 +tlvdata,offer,offer_recurrence_paywindow,paywindow,recurrence_paywindow, +tlvtype,offer,offer_recurrence_limit,30 +tlvdata,offer,offer_recurrence_limit,max_period,tu32, +tlvtype,offer,offer_recurrence_base,32 +tlvdata,offer,offer_recurrence_base,base,recurrence_base, +tlvtype,invoice_request,invreq_metadata,0 +tlvdata,invoice_request,invreq_metadata,blob,byte,... +tlvtype,invoice_request,offer_chains,2 +tlvdata,invoice_request,offer_chains,chains,chain_hash,... +tlvtype,invoice_request,offer_metadata,4 +tlvdata,invoice_request,offer_metadata,data,byte,... +tlvtype,invoice_request,offer_currency,6 +tlvdata,invoice_request,offer_currency,iso4217,utf8,... +tlvtype,invoice_request,offer_amount,8 +tlvdata,invoice_request,offer_amount,amount,tu64, +tlvtype,invoice_request,offer_description,10 +tlvdata,invoice_request,offer_description,description,utf8,... +tlvtype,invoice_request,offer_features,12 +tlvdata,invoice_request,offer_features,features,byte,... +tlvtype,invoice_request,offer_absolute_expiry,14 +tlvdata,invoice_request,offer_absolute_expiry,seconds_from_epoch,tu64, +tlvtype,invoice_request,offer_paths,16 +tlvdata,invoice_request,offer_paths,paths,blinded_path,... +tlvtype,invoice_request,offer_issuer,18 +tlvdata,invoice_request,offer_issuer,issuer,utf8,... +tlvtype,invoice_request,offer_quantity_max,20 +tlvdata,invoice_request,offer_quantity_max,max,tu64, +tlvtype,invoice_request,offer_node_id,22 +tlvdata,invoice_request,offer_node_id,node_id,point, +tlvtype,invoice_request,offer_recurrence,26 +tlvdata,invoice_request,offer_recurrence,recurrence,recurrence, +tlvtype,invoice_request,offer_recurrence_paywindow,28 +tlvdata,invoice_request,offer_recurrence_paywindow,paywindow,recurrence_paywindow, +tlvtype,invoice_request,offer_recurrence_limit,30 +tlvdata,invoice_request,offer_recurrence_limit,max_period,tu32, +tlvtype,invoice_request,offer_recurrence_base,32 +tlvdata,invoice_request,offer_recurrence_base,base,recurrence_base, +tlvtype,invoice_request,invreq_chain,80 +tlvdata,invoice_request,invreq_chain,chain,chain_hash, +tlvtype,invoice_request,invreq_amount,82 +tlvdata,invoice_request,invreq_amount,msat,tu64, +tlvtype,invoice_request,invreq_features,84 +tlvdata,invoice_request,invreq_features,features,byte,... +tlvtype,invoice_request,invreq_quantity,86 +tlvdata,invoice_request,invreq_quantity,quantity,tu64, +tlvtype,invoice_request,invreq_payer_id,88 +tlvdata,invoice_request,invreq_payer_id,key,point, +tlvtype,invoice_request,invreq_payer_note,89 +tlvdata,invoice_request,invreq_payer_note,note,utf8,... +tlvtype,invoice_request,invreq_recurrence_counter,90 +tlvdata,invoice_request,invreq_recurrence_counter,counter,tu32, +tlvtype,invoice_request,invreq_recurrence_start,92 +tlvdata,invoice_request,invreq_recurrence_start,period_offset,tu32, tlvtype,invoice_request,signature,240 tlvdata,invoice_request,signature,sig,bip340sig, -tlvtype,invoice,chain,3 -tlvdata,invoice,chain,chain,chain_hash, -tlvtype,invoice,offer_id,4 -tlvdata,invoice,offer_id,offer_id,sha256, -tlvtype,invoice,amount,8 -tlvdata,invoice,amount,msat,tu64, -tlvtype,invoice,description,10 -tlvdata,invoice,description,description,utf8,... -tlvtype,invoice,features,12 -tlvdata,invoice,features,features,byte,... -tlvtype,invoice,paths,16 -tlvdata,invoice,paths,paths,blinded_path,... -tlvtype,invoice,blindedpay,18 -tlvdata,invoice,blindedpay,payinfo,blinded_payinfo,... -tlvtype,invoice,blinded_capacities,19 -tlvdata,invoice,blinded_capacities,incoming_msat,u64,... -tlvtype,invoice,issuer,20 -tlvdata,invoice,issuer,issuer,utf8,... -tlvtype,invoice,node_id,30 -tlvdata,invoice,node_id,node_id,point, -tlvtype,invoice,quantity,32 -tlvdata,invoice,quantity,quantity,tu64, -tlvtype,invoice,refund_for,34 -tlvdata,invoice,refund_for,refunded_payment_hash,sha256, -tlvtype,invoice,recurrence_counter,36 -tlvdata,invoice,recurrence_counter,counter,tu32, -tlvtype,invoice,recurrence_start,68 -tlvdata,invoice,recurrence_start,period_offset,tu32, -tlvtype,invoice,recurrence_basetime,64 -tlvdata,invoice,recurrence_basetime,basetime,tu64, -tlvtype,invoice,payer_key,38 -tlvdata,invoice,payer_key,key,point, -tlvtype,invoice,payer_note,39 -tlvdata,invoice,payer_note,note,utf8,... -tlvtype,invoice,created_at,40 -tlvdata,invoice,created_at,timestamp,tu64, -tlvtype,invoice,payment_hash,42 -tlvdata,invoice,payment_hash,payment_hash,sha256, -tlvtype,invoice,relative_expiry,44 -tlvdata,invoice,relative_expiry,seconds_from_creation,tu32, -tlvtype,invoice,cltv,46 -tlvdata,invoice,cltv,min_final_cltv_expiry,tu16, -tlvtype,invoice,fallbacks,48 -tlvdata,invoice,fallbacks,fallbacks,fallback_address,... -tlvtype,invoice,payer_info,50 -tlvdata,invoice,payer_info,blob,byte,... -tlvtype,invoice,refund_signature,52 -tlvdata,invoice,refund_signature,payer_signature,bip340sig, -tlvtype,invoice,send_invoice,54 -tlvtype,invoice,replace_invoice,56 -tlvdata,invoice,replace_invoice,payment_hash,sha256, +tlvtype,invoice,invreq_metadata,0 +tlvdata,invoice,invreq_metadata,blob,byte,... +tlvtype,invoice,offer_chains,2 +tlvdata,invoice,offer_chains,chains,chain_hash,... +tlvtype,invoice,offer_metadata,4 +tlvdata,invoice,offer_metadata,data,byte,... +tlvtype,invoice,offer_currency,6 +tlvdata,invoice,offer_currency,iso4217,utf8,... +tlvtype,invoice,offer_amount,8 +tlvdata,invoice,offer_amount,amount,tu64, +tlvtype,invoice,offer_description,10 +tlvdata,invoice,offer_description,description,utf8,... +tlvtype,invoice,offer_features,12 +tlvdata,invoice,offer_features,features,byte,... +tlvtype,invoice,offer_absolute_expiry,14 +tlvdata,invoice,offer_absolute_expiry,seconds_from_epoch,tu64, +tlvtype,invoice,offer_paths,16 +tlvdata,invoice,offer_paths,paths,blinded_path,... +tlvtype,invoice,offer_issuer,18 +tlvdata,invoice,offer_issuer,issuer,utf8,... +tlvtype,invoice,offer_quantity_max,20 +tlvdata,invoice,offer_quantity_max,max,tu64, +tlvtype,invoice,offer_node_id,22 +tlvdata,invoice,offer_node_id,node_id,point, +tlvtype,invoice,offer_recurrence,26 +tlvdata,invoice,offer_recurrence,recurrence,recurrence, +tlvtype,invoice,offer_recurrence_paywindow,28 +tlvdata,invoice,offer_recurrence_paywindow,paywindow,recurrence_paywindow, +tlvtype,invoice,offer_recurrence_limit,30 +tlvdata,invoice,offer_recurrence_limit,max_period,tu32, +tlvtype,invoice,offer_recurrence_base,32 +tlvdata,invoice,offer_recurrence_base,base,recurrence_base, +tlvtype,invoice,invreq_chain,80 +tlvdata,invoice,invreq_chain,chain,chain_hash, +tlvtype,invoice,invreq_amount,82 +tlvdata,invoice,invreq_amount,msat,tu64, +tlvtype,invoice,invreq_features,84 +tlvdata,invoice,invreq_features,features,byte,... +tlvtype,invoice,invreq_quantity,86 +tlvdata,invoice,invreq_quantity,quantity,tu64, +tlvtype,invoice,invreq_payer_id,88 +tlvdata,invoice,invreq_payer_id,key,point, +tlvtype,invoice,invreq_payer_note,89 +tlvdata,invoice,invreq_payer_note,note,utf8,... +tlvtype,invoice,invreq_recurrence_counter,90 +tlvdata,invoice,invreq_recurrence_counter,counter,tu32, +tlvtype,invoice,invreq_recurrence_start,92 +tlvdata,invoice,invreq_recurrence_start,period_offset,tu32, +tlvtype,invoice,invoice_paths,160 +tlvdata,invoice,invoice_paths,paths,blinded_path,... +tlvtype,invoice,invoice_blindedpay,162 +tlvdata,invoice,invoice_blindedpay,payinfo,blinded_payinfo,... +tlvtype,invoice,invoice_created_at,164 +tlvdata,invoice,invoice_created_at,timestamp,tu64, +tlvtype,invoice,invoice_relative_expiry,166 +tlvdata,invoice,invoice_relative_expiry,seconds_from_creation,tu32, +tlvtype,invoice,invoice_payment_hash,168 +tlvdata,invoice,invoice_payment_hash,payment_hash,sha256, +tlvtype,invoice,invoice_amount,170 +tlvdata,invoice,invoice_amount,msat,tu64, +tlvtype,invoice,invoice_fallbacks,172 +tlvdata,invoice,invoice_fallbacks,fallbacks,fallback_address,... +tlvtype,invoice,invoice_features,174 +tlvdata,invoice,invoice_features,features,byte,... +tlvtype,invoice,invoice_node_id,176 +tlvdata,invoice,invoice_node_id,node_id,point, +tlvtype,invoice,invoice_recurrence_basetime,178 +tlvdata,invoice,invoice_recurrence_basetime,basetime,tu64, tlvtype,invoice,signature,240 tlvdata,invoice,signature,sig,bip340sig, +subtype,recurrence +subtypedata,recurrence,time_unit,byte, +subtypedata,recurrence,period,tu32, +subtype,recurrence_paywindow +subtypedata,recurrence_paywindow,seconds_before,u32, +subtypedata,recurrence_paywindow,proportional_amount,byte, +subtypedata,recurrence_paywindow,seconds_after,tu32, +subtype,recurrence_base +subtypedata,recurrence_base,start_any_period,byte, +subtypedata,recurrence_base,basetime,tu64, subtype,blinded_payinfo subtypedata,blinded_payinfo,fee_base_msat,u32, subtypedata,blinded_payinfo,fee_proportional_millionths,u32, diff --git a/wire/bolt12_wire.csv b/wire/bolt12_wire.csv index 8c20ced38ef9..10ba1faf0b1c 100644 --- a/wire/bolt12_wire.csv +++ b/wire/bolt12_wire.csv @@ -1,119 +1,163 @@ -tlvtype,offer,chains,2 -tlvdata,offer,chains,chains,chain_hash,... -tlvtype,offer,currency,6 -tlvdata,offer,currency,iso4217,utf8,... -tlvtype,offer,amount,8 -tlvdata,offer,amount,amount,tu64, -tlvtype,offer,description,10 -tlvdata,offer,description,description,utf8,... -tlvtype,offer,features,12 -tlvdata,offer,features,features,byte,... -tlvtype,offer,absolute_expiry,14 -tlvdata,offer,absolute_expiry,seconds_from_epoch,tu64, -tlvtype,offer,paths,16 -tlvdata,offer,paths,paths,blinded_path,... -tlvtype,offer,issuer,20 -tlvdata,offer,issuer,issuer,utf8,... -tlvtype,offer,quantity_min,22 -tlvdata,offer,quantity_min,min,tu64, -tlvtype,offer,quantity_max,24 -tlvdata,offer,quantity_max,max,tu64, -tlvtype,offer,recurrence,26 -tlvdata,offer,recurrence,time_unit,byte, -tlvdata,offer,recurrence,period,tu32, -tlvtype,offer,recurrence_paywindow,64 -tlvdata,offer,recurrence_paywindow,seconds_before,u32, -tlvdata,offer,recurrence_paywindow,proportional_amount,byte, -tlvdata,offer,recurrence_paywindow,seconds_after,tu32, -tlvtype,offer,recurrence_limit,66 -tlvdata,offer,recurrence_limit,max_period,tu32, -tlvtype,offer,recurrence_base,28 -tlvdata,offer,recurrence_base,start_any_period,byte, -tlvdata,offer,recurrence_base,basetime,tu64, -tlvtype,offer,node_id,30 -tlvdata,offer,node_id,node_id,point, -tlvtype,offer,send_invoice,54 -tlvtype,offer,refund_for,34 -tlvdata,offer,refund_for,refunded_payment_hash,sha256, -tlvtype,offer,signature,240 -tlvdata,offer,signature,sig,bip340sig, -tlvtype,invoice_request,chain,3 -tlvdata,invoice_request,chain,chain,chain_hash, -tlvtype,invoice_request,offer_id,4 -tlvdata,invoice_request,offer_id,offer_id,sha256, -tlvtype,invoice_request,amount,8 -tlvdata,invoice_request,amount,msat,tu64, -tlvtype,invoice_request,features,12 -tlvdata,invoice_request,features,features,byte,... -tlvtype,invoice_request,quantity,32 -tlvdata,invoice_request,quantity,quantity,tu64, -tlvtype,invoice_request,recurrence_counter,36 -tlvdata,invoice_request,recurrence_counter,counter,tu32, -tlvtype,invoice_request,recurrence_start,68 -tlvdata,invoice_request,recurrence_start,period_offset,tu32, -tlvtype,invoice_request,payer_key,38 -tlvdata,invoice_request,payer_key,key,point, -tlvtype,invoice_request,payer_note,39 -tlvdata,invoice_request,payer_note,note,utf8,... -tlvtype,invoice_request,payer_info,50 -tlvdata,invoice_request,payer_info,blob,byte,... -tlvtype,invoice_request,replace_invoice,56 -tlvdata,invoice_request,replace_invoice,payment_hash,sha256, +tlvtype,offer,offer_chains,2 +tlvdata,offer,offer_chains,chains,chain_hash,... +tlvtype,offer,offer_metadata,4 +tlvdata,offer,offer_metadata,data,byte,... +tlvtype,offer,offer_currency,6 +tlvdata,offer,offer_currency,iso4217,utf8,... +tlvtype,offer,offer_amount,8 +tlvdata,offer,offer_amount,amount,tu64, +tlvtype,offer,offer_description,10 +tlvdata,offer,offer_description,description,utf8,... +tlvtype,offer,offer_features,12 +tlvdata,offer,offer_features,features,byte,... +tlvtype,offer,offer_absolute_expiry,14 +tlvdata,offer,offer_absolute_expiry,seconds_from_epoch,tu64, +tlvtype,offer,offer_paths,16 +tlvdata,offer,offer_paths,paths,blinded_path,... +tlvtype,offer,offer_issuer,18 +tlvdata,offer,offer_issuer,issuer,utf8,... +tlvtype,offer,offer_quantity_max,20 +tlvdata,offer,offer_quantity_max,max,tu64, +tlvtype,offer,offer_node_id,22 +tlvdata,offer,offer_node_id,node_id,point, +tlvtype,offer,offer_recurrence,26 +tlvdata,offer,offer_recurrence,recurrence,recurrence, +tlvtype,offer,offer_recurrence_paywindow,28 +tlvdata,offer,offer_recurrence_paywindow,paywindow,recurrence_paywindow, +tlvtype,offer,offer_recurrence_limit,30 +tlvdata,offer,offer_recurrence_limit,max_period,tu32, +tlvtype,offer,offer_recurrence_base,32 +tlvdata,offer,offer_recurrence_base,base,recurrence_base, +tlvtype,invoice_request,invreq_metadata,0 +tlvdata,invoice_request,invreq_metadata,blob,byte,... +tlvtype,invoice_request,offer_chains,2 +tlvdata,invoice_request,offer_chains,chains,chain_hash,... +tlvtype,invoice_request,offer_metadata,4 +tlvdata,invoice_request,offer_metadata,data,byte,... +tlvtype,invoice_request,offer_currency,6 +tlvdata,invoice_request,offer_currency,iso4217,utf8,... +tlvtype,invoice_request,offer_amount,8 +tlvdata,invoice_request,offer_amount,amount,tu64, +tlvtype,invoice_request,offer_description,10 +tlvdata,invoice_request,offer_description,description,utf8,... +tlvtype,invoice_request,offer_features,12 +tlvdata,invoice_request,offer_features,features,byte,... +tlvtype,invoice_request,offer_absolute_expiry,14 +tlvdata,invoice_request,offer_absolute_expiry,seconds_from_epoch,tu64, +tlvtype,invoice_request,offer_paths,16 +tlvdata,invoice_request,offer_paths,paths,blinded_path,... +tlvtype,invoice_request,offer_issuer,18 +tlvdata,invoice_request,offer_issuer,issuer,utf8,... +tlvtype,invoice_request,offer_quantity_max,20 +tlvdata,invoice_request,offer_quantity_max,max,tu64, +tlvtype,invoice_request,offer_node_id,22 +tlvdata,invoice_request,offer_node_id,node_id,point, +tlvtype,invoice_request,offer_recurrence,26 +tlvdata,invoice_request,offer_recurrence,recurrence,recurrence, +tlvtype,invoice_request,offer_recurrence_paywindow,28 +tlvdata,invoice_request,offer_recurrence_paywindow,paywindow,recurrence_paywindow, +tlvtype,invoice_request,offer_recurrence_limit,30 +tlvdata,invoice_request,offer_recurrence_limit,max_period,tu32, +tlvtype,invoice_request,offer_recurrence_base,32 +tlvdata,invoice_request,offer_recurrence_base,base,recurrence_base, +tlvtype,invoice_request,invreq_chain,80 +tlvdata,invoice_request,invreq_chain,chain,chain_hash, +tlvtype,invoice_request,invreq_amount,82 +tlvdata,invoice_request,invreq_amount,msat,tu64, +tlvtype,invoice_request,invreq_features,84 +tlvdata,invoice_request,invreq_features,features,byte,... +tlvtype,invoice_request,invreq_quantity,86 +tlvdata,invoice_request,invreq_quantity,quantity,tu64, +tlvtype,invoice_request,invreq_payer_id,88 +tlvdata,invoice_request,invreq_payer_id,key,point, +tlvtype,invoice_request,invreq_payer_note,89 +tlvdata,invoice_request,invreq_payer_note,note,utf8,... +tlvtype,invoice_request,invreq_recurrence_counter,90 +tlvdata,invoice_request,invreq_recurrence_counter,counter,tu32, +tlvtype,invoice_request,invreq_recurrence_start,92 +tlvdata,invoice_request,invreq_recurrence_start,period_offset,tu32, tlvtype,invoice_request,signature,240 tlvdata,invoice_request,signature,sig,bip340sig, -tlvtype,invoice,chain,3 -tlvdata,invoice,chain,chain,chain_hash, -tlvtype,invoice,offer_id,4 -tlvdata,invoice,offer_id,offer_id,sha256, -tlvtype,invoice,amount,8 -tlvdata,invoice,amount,msat,tu64, -tlvtype,invoice,description,10 -tlvdata,invoice,description,description,utf8,... -tlvtype,invoice,features,12 -tlvdata,invoice,features,features,byte,... -tlvtype,invoice,paths,16 -tlvdata,invoice,paths,paths,blinded_path,... -tlvtype,invoice,blindedpay,18 -tlvdata,invoice,blindedpay,payinfo,blinded_payinfo,... -tlvtype,invoice,blinded_capacities,19 -tlvdata,invoice,blinded_capacities,incoming_msat,u64,... -tlvtype,invoice,issuer,20 -tlvdata,invoice,issuer,issuer,utf8,... -tlvtype,invoice,node_id,30 -tlvdata,invoice,node_id,node_id,point, -tlvtype,invoice,quantity,32 -tlvdata,invoice,quantity,quantity,tu64, -tlvtype,invoice,refund_for,34 -tlvdata,invoice,refund_for,refunded_payment_hash,sha256, -tlvtype,invoice,recurrence_counter,36 -tlvdata,invoice,recurrence_counter,counter,tu32, -tlvtype,invoice,recurrence_start,68 -tlvdata,invoice,recurrence_start,period_offset,tu32, -tlvtype,invoice,recurrence_basetime,64 -tlvdata,invoice,recurrence_basetime,basetime,tu64, -tlvtype,invoice,payer_key,38 -tlvdata,invoice,payer_key,key,point, -tlvtype,invoice,payer_note,39 -tlvdata,invoice,payer_note,note,utf8,... -tlvtype,invoice,created_at,40 -tlvdata,invoice,created_at,timestamp,tu64, -tlvtype,invoice,payment_hash,42 -tlvdata,invoice,payment_hash,payment_hash,sha256, -tlvtype,invoice,relative_expiry,44 -tlvdata,invoice,relative_expiry,seconds_from_creation,tu32, -tlvtype,invoice,cltv,46 -tlvdata,invoice,cltv,min_final_cltv_expiry,tu16, -tlvtype,invoice,fallbacks,48 -tlvdata,invoice,fallbacks,fallbacks,fallback_address,... -tlvtype,invoice,payer_info,50 -tlvdata,invoice,payer_info,blob,byte,... -tlvtype,invoice,refund_signature,52 -tlvdata,invoice,refund_signature,payer_signature,bip340sig, -tlvtype,invoice,send_invoice,54 -tlvtype,invoice,replace_invoice,56 -tlvdata,invoice,replace_invoice,payment_hash,sha256, +tlvtype,invoice,invreq_metadata,0 +tlvdata,invoice,invreq_metadata,blob,byte,... +tlvtype,invoice,offer_chains,2 +tlvdata,invoice,offer_chains,chains,chain_hash,... +tlvtype,invoice,offer_metadata,4 +tlvdata,invoice,offer_metadata,data,byte,... +tlvtype,invoice,offer_currency,6 +tlvdata,invoice,offer_currency,iso4217,utf8,... +tlvtype,invoice,offer_amount,8 +tlvdata,invoice,offer_amount,amount,tu64, +tlvtype,invoice,offer_description,10 +tlvdata,invoice,offer_description,description,utf8,... +tlvtype,invoice,offer_features,12 +tlvdata,invoice,offer_features,features,byte,... +tlvtype,invoice,offer_absolute_expiry,14 +tlvdata,invoice,offer_absolute_expiry,seconds_from_epoch,tu64, +tlvtype,invoice,offer_paths,16 +tlvdata,invoice,offer_paths,paths,blinded_path,... +tlvtype,invoice,offer_issuer,18 +tlvdata,invoice,offer_issuer,issuer,utf8,... +tlvtype,invoice,offer_quantity_max,20 +tlvdata,invoice,offer_quantity_max,max,tu64, +tlvtype,invoice,offer_node_id,22 +tlvdata,invoice,offer_node_id,node_id,point, +tlvtype,invoice,offer_recurrence,26 +tlvdata,invoice,offer_recurrence,recurrence,recurrence, +tlvtype,invoice,offer_recurrence_paywindow,28 +tlvdata,invoice,offer_recurrence_paywindow,paywindow,recurrence_paywindow, +tlvtype,invoice,offer_recurrence_limit,30 +tlvdata,invoice,offer_recurrence_limit,max_period,tu32, +tlvtype,invoice,offer_recurrence_base,32 +tlvdata,invoice,offer_recurrence_base,base,recurrence_base, +tlvtype,invoice,invreq_chain,80 +tlvdata,invoice,invreq_chain,chain,chain_hash, +tlvtype,invoice,invreq_amount,82 +tlvdata,invoice,invreq_amount,msat,tu64, +tlvtype,invoice,invreq_features,84 +tlvdata,invoice,invreq_features,features,byte,... +tlvtype,invoice,invreq_quantity,86 +tlvdata,invoice,invreq_quantity,quantity,tu64, +tlvtype,invoice,invreq_payer_id,88 +tlvdata,invoice,invreq_payer_id,key,point, +tlvtype,invoice,invreq_payer_note,89 +tlvdata,invoice,invreq_payer_note,note,utf8,... +tlvtype,invoice,invreq_recurrence_counter,90 +tlvdata,invoice,invreq_recurrence_counter,counter,tu32, +tlvtype,invoice,invreq_recurrence_start,92 +tlvdata,invoice,invreq_recurrence_start,period_offset,tu32, +tlvtype,invoice,invoice_paths,160 +tlvdata,invoice,invoice_paths,paths,blinded_path,... +tlvtype,invoice,invoice_blindedpay,162 +tlvdata,invoice,invoice_blindedpay,payinfo,blinded_payinfo,... +tlvtype,invoice,invoice_created_at,164 +tlvdata,invoice,invoice_created_at,timestamp,tu64, +tlvtype,invoice,invoice_relative_expiry,166 +tlvdata,invoice,invoice_relative_expiry,seconds_from_creation,tu32, +tlvtype,invoice,invoice_payment_hash,168 +tlvdata,invoice,invoice_payment_hash,payment_hash,sha256, +tlvtype,invoice,invoice_amount,170 +tlvdata,invoice,invoice_amount,msat,tu64, +tlvtype,invoice,invoice_fallbacks,172 +tlvdata,invoice,invoice_fallbacks,fallbacks,fallback_address,... +tlvtype,invoice,invoice_features,174 +tlvdata,invoice,invoice_features,features,byte,... +tlvtype,invoice,invoice_node_id,176 +tlvdata,invoice,invoice_node_id,node_id,point, +tlvtype,invoice,invoice_recurrence_basetime,178 +tlvdata,invoice,invoice_recurrence_basetime,basetime,tu64, tlvtype,invoice,signature,240 tlvdata,invoice,signature,sig,bip340sig, +subtype,recurrence +subtypedata,recurrence,time_unit,byte, +subtypedata,recurrence,period,tu32, +subtype,recurrence_paywindow +subtypedata,recurrence_paywindow,seconds_before,u32, +subtypedata,recurrence_paywindow,proportional_amount,byte, +subtypedata,recurrence_paywindow,seconds_after,tu32, +subtype,recurrence_base +subtypedata,recurrence_base,start_any_period,byte, +subtypedata,recurrence_base,basetime,tu64, subtype,blinded_payinfo subtypedata,blinded_payinfo,fee_base_msat,u32, subtypedata,blinded_payinfo,fee_proportional_millionths,u32, diff --git a/wire/extracted_bolt12_01_recurrence.patch b/wire/extracted_bolt12_01_recurrence.patch index 5615856f1cfe..bde21b180591 100644 --- a/wire/extracted_bolt12_01_recurrence.patch +++ b/wire/extracted_bolt12_01_recurrence.patch @@ -1,47 +1,87 @@ -diff --git b/wire/bolt12_wire.csv a/wire/bolt12_wire.csv -index 726c3c0a1..a53ca3cdf 100644 ---- b/wire/bolt12_wire.csv -+++ a/wire/bolt12_wire.csv -@@ -18,6 +18,18 @@ tlvtype,offer,quantity_min,22 - tlvdata,offer,quantity_min,min,tu64, - tlvtype,offer,quantity_max,24 - tlvdata,offer,quantity_max,max,tu64, -+tlvtype,offer,recurrence,26 -+tlvdata,offer,recurrence,time_unit,byte, -+tlvdata,offer,recurrence,period,tu32, -+tlvtype,offer,recurrence_paywindow,64 -+tlvdata,offer,recurrence_paywindow,seconds_before,u32, -+tlvdata,offer,recurrence_paywindow,proportional_amount,byte, -+tlvdata,offer,recurrence_paywindow,seconds_after,tu32, -+tlvtype,offer,recurrence_limit,66 -+tlvdata,offer,recurrence_limit,max_period,tu32, -+tlvtype,offer,recurrence_base,28 -+tlvdata,offer,recurrence_base,start_any_period,byte, -+tlvdata,offer,recurrence_base,basetime,tu64, - tlvtype,offer,node_id,30 - tlvdata,offer,node_id,node_id,pubkey, - tlvtype,offer,send_invoice,54 -@@ -40,6 +54,10 @@ tlvtype,invoice_request,features,12 - tlvdata,invoice_request,features,features,byte,... - tlvtype,invoice_request,quantity,32 - tlvdata,invoice_request,quantity,quantity,tu64, -+tlvtype,invoice_request,recurrence_counter,36 -+tlvdata,invoice_request,recurrence_counter,counter,tu32, -+tlvtype,invoice_request,recurrence_start,68 -+tlvdata,invoice_request,recurrence_start,period_offset,tu32, - tlvtype,invoice_request,payer_key,38 - tlvdata,invoice_request,payer_key,key,pubkey, - tlvtype,invoice_request,payer_note,39 -@@ -74,6 +94,12 @@ tlvtype,invoice,quantity,32 - tlvdata,invoice,quantity,quantity,tu64, - tlvtype,invoice,refund_for,34 - tlvdata,invoice,refund_for,refunded_payment_hash,sha256, -+tlvtype,invoice,recurrence_counter,36 -+tlvdata,invoice,recurrence_counter,counter,tu32, -+tlvtype,invoice,recurrence_start,68 -+tlvdata,invoice,recurrence_start,period_offset,tu32, -+tlvtype,invoice,recurrence_basetime,64 -+tlvdata,invoice,recurrence_basetime,basetime,tu64, - tlvtype,invoice,payer_key,38 - tlvdata,invoice,payer_key,key,pubkey, - tlvtype,invoice,payer_note,39 +--- wire/bolt12_wire.csv.raw 2022-10-04 13:26:18.105307201 +1030 ++++ wire/bolt12_wire.csv 2022-10-04 13:25:59.617242667 +1030 +@@ -22,6 +22,14 @@ + tlvdata,offer,offer_quantity_max,max,tu64, + tlvtype,offer,offer_node_id,24 + tlvdata,offer,offer_node_id,node_id,point, ++tlvtype,offer,offer_recurrence,26 ++tlvdata,offer,offer_recurrence,recurrence,recurrence, ++tlvtype,offer,offer_recurrence_paywindow,28 ++tlvdata,offer,offer_recurrence_paywindow,paywindow,recurrence_paywindow, ++tlvtype,offer,offer_recurrence_limit,30 ++tlvdata,offer,offer_recurrence_limit,max_period,tu32, ++tlvtype,offer,offer_recurrence_base,32 ++tlvdata,offer,offer_recurrence_base,base,recurrence_base, + tlvtype,invoice_request,invreq_metadata,0 + tlvdata,invoice_request,invreq_metadata,blob,byte,... + tlvtype,invoice_request,offer_chains,2 +@@ -48,6 +60,14 @@ + tlvdata,invoice_request,offer_quantity_max,max,tu64, + tlvtype,invoice_request,offer_node_id,24 + tlvdata,invoice_request,offer_node_id,node_id,point, ++tlvtype,invoice_request,offer_recurrence,26 ++tlvdata,invoice_request,offer_recurrence,recurrence,recurrence, ++tlvtype,invoice_request,offer_recurrence_paywindow,28 ++tlvdata,invoice_request,offer_recurrence_paywindow,paywindow,recurrence_paywindow, ++tlvtype,invoice_request,offer_recurrence_limit,30 ++tlvdata,invoice_request,offer_recurrence_limit,max_period,tu32, ++tlvtype,invoice_request,offer_recurrence_base,32 ++tlvdata,invoice_request,offer_recurrence_base,base,recurrence_base, + tlvtype,invoice_request,invreq_chain,80 + tlvdata,invoice_request,invreq_chain,chain,chain_hash, + tlvtype,invoice_request,invreq_amount,82 +@@ -60,6 +84,10 @@ + tlvdata,invoice_request,invreq_payer_id,key,point, + tlvtype,invoice_request,invreq_payer_note,89 + tlvdata,invoice_request,invreq_payer_note,note,utf8,... ++tlvtype,invoice_request,invreq_recurrence_counter,90 ++tlvdata,invoice_request,invreq_recurrence_counter,counter,tu32, ++tlvtype,invoice_request,invreq_recurrence_start,92 ++tlvdata,invoice_request,invreq_recurrence_start,period_offset,tu32, + tlvtype,invoice_request,signature,240 + tlvdata,invoice_request,signature,sig,bip340sig, + tlvtype,invoice,invreq_metadata,0 +@@ -89,5 +117,13 @@ + tlvtype,invoice,offer_node_id,24 + tlvdata,invoice,offer_node_id,node_id,point, ++tlvtype,invoice,offer_recurrence,26 ++tlvdata,invoice,offer_recurrence,recurrence,recurrence, ++tlvtype,invoice,offer_recurrence_paywindow,28 ++tlvdata,invoice,offer_recurrence_paywindow,paywindow,recurrence_paywindow, ++tlvtype,invoice,offer_recurrence_limit,30 ++tlvdata,invoice,offer_recurrence_limit,max_period,tu32, ++tlvtype,invoice,offer_recurrence_base,32 ++tlvdata,invoice,offer_recurrence_base,base,recurrence_base, + tlvtype,invoice,invreq_chain,80 + tlvdata,invoice,invreq_chain,chain,chain_hash, + tlvtype,invoice,invreq_amount,82 +@@ -101,6 +141,10 @@ + tlvdata,invoice,invreq_payer_id,key,point, + tlvtype,invoice,invreq_payer_note,89 + tlvdata,invoice,invreq_payer_note,note,utf8,... ++tlvtype,invoice,invreq_recurrence_counter,90 ++tlvdata,invoice,invreq_recurrence_counter,counter,tu32, ++tlvtype,invoice,invreq_recurrence_start,92 ++tlvdata,invoice,invreq_recurrence_start,period_offset,tu32, + tlvtype,invoice,invoice_paths,160 + tlvdata,invoice,invoice_paths,paths,blinded_path,... + tlvtype,invoice,invoice_blindedpay,162 +@@ -119,6 +163,18 @@ + tlvdata,invoice,invoice_features,features,byte,... + tlvtype,invoice,invoice_node_id,176 + tlvdata,invoice,invoice_node_id,node_id,point, ++tlvtype,invoice,invoice_recurrence_basetime,178 ++tlvdata,invoice,invoice_recurrence_basetime,basetime,tu64, + tlvtype,invoice,signature,240 + tlvdata,invoice,signature,sig,bip340sig, ++subtype,recurrence ++subtypedata,recurrence,time_unit,byte, ++subtypedata,recurrence,period,tu32, ++subtype,recurrence_paywindow ++subtypedata,recurrence_paywindow,seconds_before,u32, ++subtypedata,recurrence_paywindow,proportional_amount,byte, ++subtypedata,recurrence_paywindow,seconds_after,tu32, ++subtype,recurrence_base ++subtypedata,recurrence_base,start_any_period,byte, ++subtypedata,recurrence_base,basetime,tu64, + subtype,blinded_payinfo