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 <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell
2022-11-09 13:02:00 +10:30
committed by Christian Decker
parent 846a520bc2
commit 1e3cb01546
21 changed files with 1377 additions and 1379 deletions

View File

@@ -11,7 +11,7 @@
#include <time.h> #include <time.h>
/* If chains is NULL, max_num_chains is ignored */ /* If chains is NULL, max_num_chains is ignored */
static bool bolt12_chains_match(const struct bitcoin_blkid *chains, bool bolt12_chains_match(const struct bitcoin_blkid *chains,
size_t max_num_chains, size_t max_num_chains,
const struct chainparams *must_be_chain) const struct chainparams *must_be_chain)
{ {
@@ -181,26 +181,13 @@ struct tlv_offer *offer_decode(const tal_t *ctx,
*fail = check_features_and_chain(ctx, *fail = check_features_and_chain(ctx,
our_features, must_be_chain, our_features, must_be_chain,
offer->features, offer->offer_features,
BOLT12_OFFER_FEATURE, BOLT12_OFFER_FEATURE,
offer->chains, offer->offer_chains,
tal_count(offer->chains)); tal_count(offer->offer_chains));
if (*fail) if (*fail)
return tal_free(offer); 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; return offer;
} }
@@ -230,15 +217,15 @@ struct tlv_invoice_request *invrequest_decode(const tal_t *ctx,
invrequest = fromwire_tlv_invoice_request(ctx, &data, &dlen); invrequest = fromwire_tlv_invoice_request(ctx, &data, &dlen);
if (!invrequest) { if (!invrequest) {
*fail = tal_fmt(ctx, "invalid invoice_request data"); *fail = tal_fmt(ctx, "invalid invreq data");
return NULL; return NULL;
} }
*fail = check_features_and_chain(ctx, *fail = check_features_and_chain(ctx,
our_features, must_be_chain, our_features, must_be_chain,
invrequest->features, invrequest->invreq_features,
BOLT12_INVREQ_FEATURE, BOLT12_INVREQ_FEATURE,
invrequest->chain, 1); invrequest->invreq_chain, 1);
if (*fail) if (*fail)
return tal_free(invrequest); return tal_free(invrequest);
@@ -277,9 +264,9 @@ struct tlv_invoice *invoice_decode_nosig(const tal_t *ctx,
*fail = check_features_and_chain(ctx, *fail = check_features_and_chain(ctx,
our_features, must_be_chain, our_features, must_be_chain,
invoice->features, invoice->invoice_features,
BOLT12_INVOICE_FEATURE, BOLT12_INVOICE_FEATURE,
invoice->chain, 1); invoice->invreq_chain, 1);
if (*fail) if (*fail)
return tal_free(invoice); 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, u64 offer_period_start(u64 basetime, size_t n,
const struct tlv_offer_recurrence *recur) const struct recurrence *recur)
{ {
/* BOLT-offers-recurrence #12: /* BOLT-offers-recurrence #12:
* 1. A `time_unit` defining 0 (seconds), 1 (days), 2 (months), * 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, void offer_period_paywindow(const struct recurrence *recurrence,
const struct tlv_offer_recurrence_paywindow *recurrence_paywindow, const struct recurrence_paywindow *recurrence_paywindow,
const struct tlv_offer_recurrence_base *recurrence_base, const struct recurrence_base *recurrence_base,
u64 basetime, u64 period_idx, u64 basetime, u64 period_idx,
u64 *start, u64 *end) u64 *start, u64 *end)
{ {
@@ -364,9 +351,9 @@ void offer_period_paywindow(const struct tlv_offer_recurrence *recurrence,
/* BOLT-offers-recurrence #12: /* BOLT-offers-recurrence #12:
* - if the offer has a `recurrence_basetime` or the * - if the offer has a `recurrence_basetime` or the
* `recurrence_counter` is non-zero: * `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. * `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. * than `seconds_after` seconds past that period start.
*/ */
*start = pstart - recurrence_paywindow->seconds_before; *start = pstart - recurrence_paywindow->seconds_before;
@@ -381,7 +368,7 @@ void offer_period_paywindow(const struct tlv_offer_recurrence *recurrence,
} else { } else {
/* BOLT-offers-recurrence #12: /* BOLT-offers-recurrence #12:
* - otherwise: * - otherwise:
* - SHOULD NOT send an `invoice_request` with * - SHOULD NOT send an `invreq` with
* `recurrence_counter` is non-zero for a period whose * `recurrence_counter` is non-zero for a period whose
* immediate predecessor has not yet begun. * immediate predecessor has not yet begun.
*/ */
@@ -392,7 +379,7 @@ void offer_period_paywindow(const struct tlv_offer_recurrence *recurrence,
recurrence); recurrence);
/* BOLT-offers-recurrence #12: /* 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. * has already passed.
*/ */
*end = offer_period_start(basetime, period_idx+1, *end = offer_period_start(basetime, period_idx+1,
@@ -413,7 +400,8 @@ struct tlv_invoice *invoice_decode(const tal_t *ctx,
if (invoice) { if (invoice) {
*fail = check_signature(ctx, invoice->fields, *fail = check_signature(ctx, invoice->fields,
"invoice", "signature", "invoice", "signature",
invoice->node_id, invoice->signature); invoice->invoice_node_id,
invoice->signature);
if (*fail) if (*fail)
invoice = tal_free(invoice); invoice = tal_free(invoice);
} }

View File

@@ -49,13 +49,13 @@ char *invrequest_encode(const tal_t *ctx,
/** /**
* invrequest_decode - decode this complete bolt12 text into a TLV. * invrequest_decode - decode this complete bolt12 text into a TLV.
* @ctx: the context to allocate return or *@fail off. * @ctx: the context to allocate return or *@fail off.
* @b12: the invoice_request string * @b12: the invreq string
* @b12len: the invoice_request string length * @b12len: the invreq string length
* @our_features: if non-NULL, feature set to check against. * @our_features: if non-NULL, feature set to check against.
* @must_be_chain: if non-NULL, chain to enforce. * @must_be_chain: if non-NULL, chain to enforce.
* @fail: pointer to descriptive error string, set if this returns NULL. * @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, struct tlv_invoice_request *invrequest_decode(const tal_t *ctx,
const char *b12, size_t b12len, 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, bool bolt12_chain_matches(const struct bitcoin_blkid *chain,
const struct chainparams *must_be_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? */ /* Given a basetime, when does period N start? */
u64 offer_period_start(u64 basetime, size_t n, 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. */ /* Get the start and end of the payment window for period N. */
void offer_period_paywindow(const struct tlv_offer_recurrence *recurrence, void offer_period_paywindow(const struct recurrence *recurrence,
const struct tlv_offer_recurrence_paywindow *recurrence_paywindow, const struct recurrence_paywindow *recurrence_paywindow,
const struct tlv_offer_recurrence_base *recurrence_base, const struct recurrence_base *recurrence_base,
u64 basetime, u64 period_idx, u64 basetime, u64 period_idx,
u64 *period_start, u64 *period_end); u64 *period_start, u64 *period_end);

View File

@@ -192,7 +192,7 @@ int main(int argc, char *argv[])
/* We deal in UTC; mktime() uses local time */ /* We deal in UTC; mktime() uses local time */
setenv("TZ", "", 1); setenv("TZ", "", 1);
json_for_each_arr(i, t, toks) { json_for_each_arr(i, t, toks) {
struct tlv_offer_recurrence recurrence; struct recurrence recurrence;
int unit; int unit;
const jsmntok_t *exp; const jsmntok_t *exp;
u64 base, secs, n; u64 base, secs, n;

View File

@@ -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)); 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) static void print_quantity_max(u64 max)
{ {
printf("quantity_max: %"PRIu64"\n", max); printf("quantity_max: %"PRIu64"\n", max);
} }
static bool print_recurrance(const struct tlv_offer_recurrence *recurrence, static bool print_recurrance(const struct recurrence *recurrence,
const struct tlv_offer_recurrence_paywindow *paywindow, const struct recurrence_paywindow *paywindow,
const u32 *limit, const u32 *limit,
const struct tlv_offer_recurrence_base *base) const struct recurrence_base *base)
{ {
const char *unit; const char *unit;
bool ok = true; bool ok = true;
@@ -293,17 +288,6 @@ static bool print_blindedpaths(struct blinded_path **paths,
return true; 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, static bool print_signature(const char *messagename,
const char *fieldname, const char *fieldname,
const struct tlv_field *fields, const struct tlv_field *fields,
@@ -328,12 +312,6 @@ static bool print_signature(const char *messagename,
return true; 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) static void print_quantity(u64 q)
{ {
printf("quantity: %"PRIu64"\n", q); printf("quantity: %"PRIu64"\n", q);
@@ -363,13 +341,14 @@ static bool print_recurrence_counter_with_base(const u32 *recurrence_counter,
return true; return true;
} }
static void print_payer_key(const struct pubkey *payer_key, static void print_payer_id(const struct pubkey *payer_id,
const u8 *payer_info) const u8 *metadata)
{ {
printf("payer_key: %s", printf("invreq_payer_id: %s",
type_to_string(tmpctx, struct pubkey, payer_key)); type_to_string(tmpctx, struct pubkey, payer_id));
if (payer_info) if (metadata)
printf(" (payer_info %s)", tal_hex(tmpctx, payer_info)); printf(" (invreq_metadata %s)",
tal_hex(tmpctx, metadata));
printf("\n"); printf("\n");
} }
@@ -391,11 +370,6 @@ static void print_payment_hash(const struct sha256 *payment_hash)
type_to_string(tmpctx, 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) static void print_relative_expiry(u64 *created_at, u32 *relative)
{ {
/* Ignore if already malformed */ /* Ignore if already malformed */
@@ -553,42 +527,31 @@ int main(int argc, char *argv[])
if (!offer) if (!offer)
errx(ERROR_BAD_DECODE, "Bad offer: %s", fail); errx(ERROR_BAD_DECODE, "Bad offer: %s", fail);
if (offer->send_invoice) if (offer->offer_chains)
print_send_invoice(); print_chains(offer->offer_chains);
if (offer->chains) if (offer->offer_amount)
print_chains(offer->chains); well_formed &= print_amount(offer->offer_chains,
if (offer->refund_for) offer->offer_currency,
print_refund_for(offer->refund_for); *offer->offer_amount);
if (offer->amount) if (must_have(offer, offer_description))
well_formed &= print_amount(offer->chains, print_description(offer->offer_description);
offer->currency, if (offer->offer_issuer)
*offer->amount); print_issuer(offer->offer_issuer);
if (must_have(offer, description)) if (must_have(offer, offer_node_id))
print_description(offer->description); print_node_id(offer->offer_node_id);
if (offer->issuer) if (offer->offer_quantity_max)
print_issuer(offer->issuer); print_quantity_max(*offer->offer_quantity_max);
if (must_have(offer, node_id)) if (offer->offer_recurrence)
print_node_id(offer->node_id); well_formed &= print_recurrance(offer->offer_recurrence,
if (offer->quantity_min) offer->offer_recurrence_paywindow,
print_quantity_min(*offer->quantity_min); offer->offer_recurrence_limit,
if (offer->quantity_max) offer->offer_recurrence_base);
print_quantity_max(*offer->quantity_max); if (offer->offer_absolute_expiry)
if (offer->recurrence) print_absolute_expiry(*offer->offer_absolute_expiry);
well_formed &= print_recurrance(offer->recurrence, if (offer->offer_features)
offer->recurrence_paywindow, print_features(offer->offer_features);
offer->recurrence_limit, if (offer->offer_paths)
offer->recurrence_base); print_blindedpaths(offer->offer_paths, NULL);
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 (!print_extra_fields(offer->fields)) if (!print_extra_fields(offer->fields))
well_formed = false; well_formed = false;
} else if (streq(hrp, "lnr")) { } else if (streq(hrp, "lnr")) {
@@ -596,43 +559,35 @@ int main(int argc, char *argv[])
= invrequest_decode(ctx, argv[2], strlen(argv[2]), = invrequest_decode(ctx, argv[2], strlen(argv[2]),
NULL, NULL, &fail); NULL, NULL, &fail);
if (!invreq) if (!invreq)
errx(ERROR_BAD_DECODE, "Bad invoice_request: %s", fail); errx(ERROR_BAD_DECODE, "Bad invreq: %s", fail);
if (invreq->chain) if (invreq->invreq_chain)
print_chain(invreq->chain); print_chain(invreq->invreq_chain);
if (must_have(invreq, payer_key)) if (must_have(invreq, invreq_payer_id))
print_payer_key(invreq->payer_key, invreq->payer_info); print_payer_id(invreq->invreq_payer_id,
if (invreq->payer_note) invreq->invreq_metadata);
print_payer_note(invreq->payer_note); if (invreq->invreq_payer_note)
if (must_have(invreq, offer_id)) print_payer_note(invreq->invreq_payer_note);
print_offer_id(invreq->offer_id); if (must_have(invreq, invreq_amount))
if (must_have(invreq, amount)) well_formed &= print_amount(invreq->invreq_chain,
well_formed &= print_amount(invreq->chain,
NULL, NULL,
*invreq->amount); *invreq->invreq_amount);
if (invreq->features) if (invreq->invreq_features)
print_features(invreq->features); print_features(invreq->invreq_features);
if (invreq->quantity) if (invreq->invreq_quantity)
print_quantity(*invreq->quantity); print_quantity(*invreq->invreq_quantity);
if (must_have(invreq, signature)) { if (must_have(invreq, signature)) {
if (!print_signature("invoice_request", well_formed = print_signature("invoice_request",
"signature", "signature",
invreq->fields, invreq->fields,
invreq->payer_key, invreq->invreq_payer_id,
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); invreq->signature);
} }
} if (invreq->invreq_recurrence_counter) {
if (invreq->recurrence_counter) { print_recurrence_counter(invreq->invreq_recurrence_counter,
print_recurrence_counter(invreq->recurrence_counter, invreq->invreq_recurrence_start);
invreq->recurrence_start);
} else { } else {
must_not_have(invreq, recurrence_start); must_not_have(invreq, invreq_recurrence_start);
} }
if (!print_extra_fields(invreq->fields)) if (!print_extra_fields(invreq->fields))
well_formed = false; well_formed = false;
@@ -643,70 +598,55 @@ int main(int argc, char *argv[])
if (!invoice) if (!invoice)
errx(ERROR_BAD_DECODE, "Bad invoice: %s", fail); errx(ERROR_BAD_DECODE, "Bad invoice: %s", fail);
if (invoice->chain) if (invoice->invreq_chain)
print_chain(invoice->chain); print_chain(invoice->invreq_chain);
if (invoice->offer_id) { if (must_have(invoice, invoice_amount))
print_offer_id(invoice->offer_id); well_formed &= print_amount(invoice->invreq_chain,
}
if (must_have(invoice, amount))
well_formed &= print_amount(invoice->chain,
NULL, NULL,
*invoice->amount); *invoice->invoice_amount);
if (must_have(invoice, description)) if (must_have(invoice, offer_description))
print_description(invoice->description); print_description(invoice->offer_description);
if (invoice->features) if (invoice->invoice_features)
print_features(invoice->features); print_features(invoice->invoice_features);
if (invoice->paths) { if (invoice->invoice_paths) {
must_have(invoice, blindedpay); must_have(invoice, invoice_blindedpay);
well_formed &= print_blindedpaths(invoice->paths, well_formed &= print_blindedpaths(invoice->invoice_paths,
invoice->blindedpay); invoice->invoice_blindedpay);
} else } else
must_not_have(invoice, blindedpay); must_not_have(invoice, invoice_blindedpay);
if (invoice->issuer) if (invoice->offer_issuer)
print_issuer(invoice->issuer); print_issuer(invoice->offer_issuer);
if (must_have(invoice, node_id)) if (must_have(invoice, offer_node_id))
print_node_id(invoice->node_id); print_node_id(invoice->offer_node_id);
if (invoice->quantity) if (invoice->invreq_quantity)
print_quantity(*invoice->quantity); print_quantity(*invoice->invreq_quantity);
if (invoice->refund_for) { if (invoice->invreq_recurrence_counter) {
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) {
well_formed &= well_formed &=
print_recurrence_counter_with_base(invoice->recurrence_counter, print_recurrence_counter_with_base(invoice->invreq_recurrence_counter,
invoice->recurrence_start, invoice->invreq_recurrence_start,
invoice->recurrence_basetime); invoice->invoice_recurrence_basetime);
} else { } else {
must_not_have(invoice, recurrence_start); must_not_have(invoice, invreq_recurrence_start);
must_not_have(invoice, recurrence_basetime); must_not_have(invoice, invoice_recurrence_basetime);
} }
if (must_have(invoice, payer_key)) if (must_have(invoice, invreq_payer_id))
print_payer_key(invoice->payer_key, invoice->payer_info); print_payer_id(invoice->invreq_payer_id,
if (must_have(invoice, created_at)) invoice->invreq_metadata);
print_created_at(*invoice->created_at); if (must_have(invoice, invoice_created_at))
if (invoice->payer_note) print_created_at(*invoice->invoice_created_at);
print_payer_note(invoice->payer_note); if (invoice->invreq_payer_note)
print_relative_expiry(invoice->created_at, print_payer_note(invoice->invreq_payer_note);
invoice->relative_expiry); print_relative_expiry(invoice->invoice_created_at,
if (must_have(invoice, payment_hash)) invoice->invoice_relative_expiry);
print_payment_hash(invoice->payment_hash); if (must_have(invoice, invoice_payment_hash))
if (must_have(invoice, cltv)) print_payment_hash(invoice->invoice_payment_hash);
print_cltv(*invoice->cltv); if (invoice->invoice_fallbacks)
if (invoice->fallbacks) print_fallbacks(invoice->invoice_fallbacks);
print_fallbacks(invoice->fallbacks);
if (must_have(invoice, signature)) if (must_have(invoice, signature))
well_formed &= print_signature("invoice", "signature", well_formed &= print_signature("invoice", "signature",
invoice->fields, invoice->fields,
invoice->node_id, invoice->offer_node_id,
invoice->signature); invoice->signature);
if (!print_extra_fields(invoice->fields)) if (!print_extra_fields(invoice->fields))
well_formed = false; well_formed = false;

View File

@@ -50,8 +50,7 @@ If **type** is "bolt12 offer", and **valid** is *true*:
- **path** (array of objects): an individual path: - **path** (array of objects): an individual path:
- **blinded\_node\_id** (pubkey): node_id of the hop - **blinded\_node\_id** (pubkey): node_id of the hop
- **encrypted\_recipient\_data** (hex): encrypted TLV entry for this 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 (or, if 0, means any quantity)
- **quantity\_max** (u64, optional): the maximum quantity
- **recurrence** (object, optional): how often to this offer should be used: - **recurrence** (object, optional): how often to this offer should be used:
- **time\_unit** (u32): the BOLT12 time unit - **time\_unit** (u32): the BOLT12 time unit
- **period** (u32): how many *time_unit* per payment period - **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 - **created\_at** (u64): the UNIX timestamp of invoice creation
- **payment\_hash** (hex): the hash of the *payment_preimage* (always 64 characters) - **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 - **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) - **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) - **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*) - **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\_start** (u32, optional): the optional start period for a recurring payment
- **recurrence\_basetime** (u32, optional): the UNIX timestamp of the first recurrence period start - **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\_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: - **fallbacks** (array of objects, optional): onchain addresses:
- **version** (u8): Segwit address version - **version** (u8): Segwit address version
- **hex** (hex): Raw encoded segwit address - **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 - **quantity** (u64, optional): the quantity ordered
- **recurrence\_counter** (u32, optional): the 0-based counter for a recurring payment - **recurrence\_counter** (u32, optional): the 0-based counter for a recurring payment
- **recurrence\_start** (u32, optional): the optional start period 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 - **recurrence\_signature** (bip340sig, optional): the payer key signature
If **type** is "bolt12 invoice_request", and **valid** is *false*: If **type** is "bolt12 invoice_request", and **valid** is *false*:
@@ -217,4 +215,4 @@ RESOURCES
Main web site: <https://github.com/ElementsProject/lightning> Main web site: <https://github.com/ElementsProject/lightning>
[comment]: # ( SHA256STAMP:bbe57fd87e729e1203055d983a72757b9647ea67dca23c254a05b38b7b7020d9) [comment]: # ( SHA256STAMP:3f0c78dd665bff6749352801b0fdd958ee138fda6ede5b3631d9cb136fc45d76)

View File

@@ -6,7 +6,7 @@ SYNOPSIS
**(WARNING: experimental-offers only)** **(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 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 The *label* field is an internal-use name for the offer, which can
be any UTF-8 string. be any UTF-8 string.
The present of *quantity_min* or *quantity_max* indicates that the The presence of *quantity_max* indicates that the
invoice can specify more than one of the items within this (inclusive) invoice can specify more than one of the items up (and including)
range. The *amount* for the invoice will need to be multiplied this maximum: 0 is a special value meaning "no maximuim".
accordingly. These are encoded in the offer. 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, 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 in seconds since the first day of 1970 UTC. If not set, the offer

View File

@@ -170,13 +170,9 @@
} }
} }
}, },
"quantity_min": {
"type": "u64",
"description": "the minimum quantity"
},
"quantity_max": { "quantity_max": {
"type": "u64", "type": "u64",
"description": "the maximum quantity" "description": "the maximum quantity (or, if 0, means any quantity)"
}, },
"recurrence": { "recurrence": {
"type": "object", "type": "object",
@@ -281,7 +277,6 @@
"features": {}, "features": {},
"absolute_expiry": {}, "absolute_expiry": {},
"paths": {}, "paths": {},
"quantity_min": {},
"quantity_max": {}, "quantity_max": {},
"recurrence": {}, "recurrence": {},
"warning_offer_missing_description": { "warning_offer_missing_description": {
@@ -316,8 +311,7 @@
"description", "description",
"created_at", "created_at",
"payment_hash", "payment_hash",
"relative_expiry", "relative_expiry"
"min_final_cltv_expiry"
], ],
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
@@ -453,7 +447,7 @@
"type": "pubkey", "type": "pubkey",
"description": "the transient key which identifies the payer" "description": "the transient key which identifies the payer"
}, },
"payer_info": { "invreq_metadata": {
"type": "hex", "type": "hex",
"description": "the payer-provided blob to derive payer_key" "description": "the payer-provided blob to derive payer_key"
}, },
@@ -474,10 +468,6 @@
"type": "u32", "type": "u32",
"description": "the number of seconds after *created_at* when this expires" "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": { "fallbacks": {
"type": "array", "type": "array",
"description": "onchain addresses", "description": "onchain addresses",
@@ -550,12 +540,11 @@
"recurrence_start": {}, "recurrence_start": {},
"recurrence_basetime": {}, "recurrence_basetime": {},
"payer_key": {}, "payer_key": {},
"payer_info": {}, "invreq_metadata": {},
"timestamp": {}, "timestamp": {},
"created_at": {}, "created_at": {},
"payment_hash": {}, "payment_hash": {},
"relative_expiry": {}, "relative_expiry": {},
"min_final_cltv_expiry": {},
"fallbacks": { "fallbacks": {
"type": "array", "type": "array",
"items": { "items": {
@@ -681,7 +670,7 @@
"type": "pubkey", "type": "pubkey",
"description": "the transient key which identifies the payer" "description": "the transient key which identifies the payer"
}, },
"payer_info": { "invreq_metadata": {
"type": "hex", "type": "hex",
"description": "the payer-provided blob to derive payer_key" "description": "the payer-provided blob to derive payer_key"
}, },
@@ -723,7 +712,7 @@
"recurrence_counter": {}, "recurrence_counter": {},
"recurrence_start": {}, "recurrence_start": {},
"payer_key": {}, "payer_key": {},
"payer_info": {}, "invreq_metadata": {},
"recurrence_signature": {}, "recurrence_signature": {},
"warning_invoice_request_missing_offer_id": { "warning_invoice_request_missing_offer_id": {
"type": "string", "type": "string",

View File

@@ -70,10 +70,11 @@ static void json_add_invoice_fields(struct json_stream *response,
tinv = invoice_decode(tmpctx, tinv = invoice_decode(tmpctx,
inv->invstring, strlen(inv->invstring), inv->invstring, strlen(inv->invstring),
NULL, NULL, &fail); 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", json_add_stringn(response, "payer_note",
tinv->payer_note, tinv->invreq_payer_note,
tal_bytelen(tinv->payer_note)); tal_bytelen(tinv->invreq_payer_note));
} }
} }
@@ -1352,11 +1353,11 @@ static struct command_result *json_listinvoices(struct command *cmd,
strlen(invstring), strlen(invstring),
cmd->ld->our_features, NULL, cmd->ld->our_features, NULL,
&fail); &fail);
if (!b12 || !b12->payment_hash) { if (!b12 || !b12->invoice_payment_hash) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Invalid invstring"); "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 = tlv_encrypted_data_tlv_new(tmpctx);
tlv->path_id = invoice_path_id(inv, tlv->path_id = invoice_path_id(inv,
&ld->invoicesecret_base, &ld->invoicesecret_base,
inv->payment_hash); inv->invoice_payment_hash);
path->path[0]->encrypted_recipient_data path->path[0]->encrypted_recipient_data
= encrypt_tlv_encrypted_data(path->path[0], = encrypt_tlv_encrypted_data(path->path[0],
@@ -1668,22 +1669,23 @@ static struct tlv_invoice *add_stub_blindedpath(const tal_t *ctx,
NULL, NULL,
&path->path[0]->blinded_node_id); &path->path[0]->blinded_node_id);
inv->paths = tal_arr(inv, struct blinded_path *, 1); inv->invoice_paths = tal_arr(inv, struct blinded_path *, 1);
inv->paths[0] = tal_steal(inv->paths, path); inv->invoice_paths[0] = tal_steal(inv->invoice_paths, path);
/* BOLT-offers #12: /* BOLT-offers #12:
* - MUST include `invoice_paths` containing one or more paths to the node. * - 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 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. * - 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->invoice_blindedpay = tal_arr(inv, struct blinded_payinfo *, 1);
inv->blindedpay[0] = tal(inv->blindedpay, struct blinded_payinfo); inv->invoice_blindedpay[0] = tal(inv->invoice_blindedpay,
inv->blindedpay[0]->fee_base_msat = 0; struct blinded_payinfo);
inv->blindedpay[0]->fee_proportional_millionths = 0; inv->invoice_blindedpay[0]->fee_base_msat = 0;
inv->blindedpay[0]->cltv_expiry_delta = ld->config.cltv_final; inv->invoice_blindedpay[0]->fee_proportional_millionths = 0;
inv->blindedpay[0]->htlc_minimum_msat = AMOUNT_MSAT(0); inv->invoice_blindedpay[0]->cltv_expiry_delta = ld->config.cltv_final;
inv->blindedpay[0]->htlc_maximum_msat = AMOUNT_MSAT(21000000 * MSAT_PER_BTC); inv->invoice_blindedpay[0]->htlc_minimum_msat = AMOUNT_MSAT(0);
inv->blindedpay[0]->features = NULL; 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 */ /* But we need to update ->fields, so re-linearize */
wire = tal_arr(tmpctx, u8, 0); 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); notify_invoice_creation(cmd->ld, b11->msat, *preimage, label);
} else { } else {
struct tlv_invoice *inv; struct tlv_invoice *inv;
struct sha256 *local_offer_id; struct sha256 offer_id, *local_offer_id;
char *b12enc; char *b12enc;
struct amount_msat msat; struct amount_msat msat;
const char *desc; const char *desc;
@@ -1771,10 +1773,16 @@ static struct command_result *json_createinvoice(struct command *cmd,
"Unparsable invoice '%s': %s", "Unparsable invoice '%s': %s",
invstring, fail); 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 /* If they don't create a blinded path, add a simple one so we
* can recognize payments (bolt12 doesn't use * can recognize payments (bolt12 doesn't use
* payment_secret) */ * payment_secret) */
if (!inv->paths) if (!inv->invoice_paths)
inv = add_stub_blindedpath(cmd, cmd->ld, inv); inv = add_stub_blindedpath(cmd, cmd->ld, inv);
if (inv->signature) if (inv->signature)
@@ -1783,54 +1791,65 @@ static struct command_result *json_createinvoice(struct command *cmd,
hsm_sign_b12_invoice(cmd->ld, inv); hsm_sign_b12_invoice(cmd->ld, inv);
b12enc = invoice_encode(cmd, inv); b12enc = invoice_encode(cmd, inv);
if (inv->offer_id if (inv->offer_node_id) {
&& wallet_offer_find(tmpctx, cmd->ld->wallet, invoice_offer_id(inv, &offer_id);
inv->offer_id, NULL, &status)) { if (wallet_offer_find(tmpctx, cmd->ld->wallet,
&offer_id, NULL, &status)) {
if (!offer_status_active(status)) if (!offer_status_active(status))
return command_fail(cmd, INVOICE_OFFER_INACTIVE, return command_fail(cmd,
INVOICE_OFFER_INACTIVE,
"offer not active"); "offer not active");
local_offer_id = inv->offer_id; local_offer_id = &offer_id;
} else
local_offer_id = NULL;
} else } else
local_offer_id = NULL; local_offer_id = NULL;
if (inv->amount) /* BOLT-offers #12:
msat = amount_msat(*inv->amount); * 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) if (inv->invoice_relative_expiry)
expiry = *inv->relative_expiry; expiry = *inv->invoice_relative_expiry;
else else
expiry = BOLT12_DEFAULT_REL_EXPIRY; expiry = BOLT12_DEFAULT_REL_EXPIRY;
if (!inv->payment_hash) if (!inv->invoice_payment_hash)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Missing payment_hash in invoice"); "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, return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Incorrect preimage"); "Incorrect preimage");
if (!inv->description) if (!inv->offer_description)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Missing description in invoice"); "Missing description in invoice");
desc = tal_strndup(cmd, desc = tal_strndup(cmd,
cast_signed(char *, inv->description), inv->offer_description,
tal_bytelen(inv->description)); tal_bytelen(inv->offer_description));
if (!wallet_invoice_create(cmd->ld->wallet, if (!wallet_invoice_create(cmd->ld->wallet,
&invoice, &invoice,
inv->amount ? &msat : NULL, &msat,
label, label,
expiry, expiry,
b12enc, b12enc,
desc, desc,
inv->features, inv->invoice_features,
preimage, preimage,
&payment_hash, &payment_hash,
local_offer_id)) local_offer_id))
return fail_exists(cmd, label); return fail_exists(cmd, label);
notify_invoice_creation(cmd->ld, notify_invoice_creation(cmd->ld, &msat, *preimage, label);
inv->amount ? &msat : NULL,
*preimage, label);
} }
response = json_stream_success(cmd); response = json_stream_success(cmd);

View File

@@ -43,9 +43,6 @@ static struct command_result *param_b12_offer(struct command *cmd,
cmd->ld->our_features, chainparams, &fail); cmd->ld->our_features, chainparams, &fail);
if (!*offer) if (!*offer)
return command_fail_badparam(cmd, name, buffer, tok, fail); 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; return NULL;
} }
@@ -86,7 +83,7 @@ static struct command_result *json_createoffer(struct command *cmd,
struct json_stream *response; struct json_stream *response;
struct json_escape *label; struct json_escape *label;
struct tlv_offer *offer; struct tlv_offer *offer;
struct sha256 merkle; struct sha256 offer_id;
const char *b12str; const char *b12str;
bool *single_use; bool *single_use;
enum offer_status status; enum offer_status status;
@@ -103,15 +100,14 @@ static struct command_result *json_createoffer(struct command *cmd,
status = OFFER_SINGLE_USE_UNUSED; status = OFFER_SINGLE_USE_UNUSED;
else else
status = OFFER_MULTIPLE_USE_UNUSED; status = OFFER_MULTIPLE_USE_UNUSED;
merkle_tlv(offer->fields, &merkle);
offer->signature = NULL;
b12str = offer_encode(cmd, offer); b12str = offer_encode(cmd, offer);
offer_offer_id(offer, &offer_id);
/* If it already exists, we use that one instead (and then /* If it already exists, we use that one instead (and then
* the offer plugin will complain if it's inactive or expired) */ * 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)) { 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 **, cast_const2(const struct json_escape **,
&label), &label),
&status)) { &status)) {
@@ -122,10 +118,8 @@ static struct command_result *json_createoffer(struct command *cmd,
} else } else
created = true; created = true;
offer->signature = tal_free(offer->signature);
response = json_stream_success(cmd); 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); json_add_bool(response, "created", created);
return command_success(cmd, response); return command_success(cmd, response);
} }
@@ -240,7 +234,7 @@ static const struct json_command disableoffer_command = {
AUTODATA(json_command, &disableoffer_command); AUTODATA(json_command, &disableoffer_command);
/* We do some sanity checks now, since we're looking up prev payment anyway, /* 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, static struct command_result *prev_payment(struct command *cmd,
const char *label, const char *label,
struct tlv_invoice_request *invreq, struct tlv_invoice_request *invreq,
@@ -248,13 +242,16 @@ static struct command_result *prev_payment(struct command *cmd,
{ {
const struct wallet_payment **payments; const struct wallet_payment **payments;
bool prev_paid = false; 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); payments = wallet_payment_list(cmd, cmd->ld->wallet, NULL);
for (size_t i = 0; i < tal_count(payments); i++) { for (size_t i = 0; i < tal_count(payments); i++) {
const struct tlv_invoice *inv; const struct tlv_invoice *inv;
char *fail; char *fail;
struct sha256 inv_oid;
/* FIXME: Restrict db queries instead */ /* FIXME: Restrict db queries instead */
if (!payments[i]->label || !streq(label, payments[i]->label)) if (!payments[i]->label || !streq(label, payments[i]->label))
@@ -270,12 +267,13 @@ static struct command_result *prev_payment(struct command *cmd,
continue; continue;
/* They can reuse labels across different offers. */ /* 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; continue;
/* Be paranoid, in case someone inserts their own /* Be paranoid, in case someone inserts their own
* clashing label! */ * clashing label! */
if (!inv->recurrence_counter) if (!inv->invreq_recurrence_counter)
continue; continue;
/* BOLT-offers-recurrence #12: /* 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 * - MUST set `period_offset` to the same value on all
* following requests. * following requests.
*/ */
if (invreq->recurrence_start) { if (invreq->invreq_recurrence_start) {
if (!inv->recurrence_start) if (!inv->invreq_recurrence_start)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"unexpected" "unexpected"
" recurrence_start"); " 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, return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"recurrence_start was" "recurrence_start was"
" previously %u", " previously %u",
*inv->recurrence_start); *inv->invreq_recurrence_start);
} else { } else {
if (inv->recurrence_start) if (inv->invreq_recurrence_start)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"missing" "missing"
" recurrence_start"); " 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) if (payments[i]->status == PAYMENT_COMPLETE)
prev_paid = true; prev_paid = true;
} }
if (inv->payer_info) { if (inv->invreq_metadata) {
invreq->payer_info invreq->invreq_metadata
= tal_dup_talarr(invreq, u8, inv->payer_info); = tal_dup_talarr(invreq, u8, inv->invreq_metadata);
*prev_basetime = tal_dup(cmd, u64, *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; break;
} }
if (!invreq->payer_info) if (!invreq->invreq_metadata)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"No previous payment attempted for this" "No previous payment attempted for this"
" label and offer"); " 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); return command_fail_badparam(cmd, name, buffer, tok, fail);
#if !DEVELOPER #if !DEVELOPER
/* We use this for testing with known payer_info */ /* 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, return command_fail_badparam(cmd, name, buffer, tok,
"must not have payer_info"); "must not have invreq_metadata");
#endif #endif
if ((*invreq)->payer_key) if ((*invreq)->invreq_payer_id)
return command_fail_badparam(cmd, name, buffer, tok, return command_fail_badparam(cmd, name, buffer, tok,
"must not have payer_key"); "must not have invreq_payer_id");
return NULL; return NULL;
} }
@@ -389,12 +387,14 @@ static struct command_result *json_createinvoicerequest(struct command *cmd,
NULL)) NULL))
return command_param_failed(); 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) if (!label)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Need payment label for recurring payments"); "Need payment label for recurring payments");
if (*invreq->recurrence_counter != 0) { if (*invreq->invreq_recurrence_counter != 0) {
struct command_result *err struct command_result *err
= prev_payment(cmd, label, invreq, = prev_payment(cmd, label, invreq,
&prev_basetime); &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: /* BOLT-offers #12:
* `payer_info` might typically contain information about the *
* derivation of the `payer_key`. This should not leak any * `invreq_metadata` might typically contain information about
* information (such as using a simple BIP-32 derivation * the derivation of the `invreq_payer_id`. This should not
* path); a valid system might be for a node to maintain a * leak any information (such as using a simple BIP-32
* base payer key, and encode a 128-bit tweak here. The * derivation path); a valid system might be for a node to
* payer_key would be derived by tweaking the base key with * maintain a base payer key and encode a 128-bit tweak here.
* SHA256(payer_base_pubkey || tweak). * 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); invreq->invreq_metadata = tal_arr(invreq, u8, 16);
randombytes_buf(invreq->payer_info, randombytes_buf(invreq->invreq_metadata,
tal_bytelen(invreq->payer_info)); 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, if (!payer_key(cmd->ld,
invreq->payer_info, tal_bytelen(invreq->payer_info), invreq->invreq_metadata,
invreq->payer_key)) { tal_bytelen(invreq->invreq_metadata),
invreq->invreq_payer_id)) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Invalid tweak"); "Invalid tweak");
} }
@@ -435,7 +439,7 @@ static struct command_result *json_createinvoicerequest(struct command *cmd,
merkle_tlv(invreq->fields, &merkle); merkle_tlv(invreq->fields, &merkle);
invreq->signature = tal(invreq, struct bip340sig); invreq->signature = tal(invreq, struct bip340sig);
hsm_sign_b12(cmd->ld, "invoice_request", "signature", 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); invreq->signature);
response = json_stream_success(cmd); response = json_stream_success(cmd);

View File

@@ -1592,8 +1592,8 @@ static struct command_result *json_listsendpays(struct command *cmd,
b12 = invoice_decode(cmd, invstring, strlen(invstring), b12 = invoice_decode(cmd, invstring, strlen(invstring),
cmd->ld->our_features, cmd->ld->our_features,
chainparams, &fail); chainparams, &fail);
if (b12 && b12->payment_hash) if (b12 && b12->invoice_payment_hash)
rhash = b12->payment_hash; rhash = b12->invoice_payment_hash;
else else
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Invalid invstring: %s", fail); "Invalid invstring: %s", fail);

View File

@@ -315,6 +315,9 @@ struct tlv_invoice *invoice_decode_nosig(const tal_t *ctx UNNEEDED,
/* Generated stub for invoice_encode */ /* Generated stub for invoice_encode */
char *invoice_encode(const tal_t *ctx UNNEEDED, const struct tlv_invoice *bolt12_tlv UNNEEDED) char *invoice_encode(const tal_t *ctx UNNEEDED, const struct tlv_invoice *bolt12_tlv UNNEEDED)
{ fprintf(stderr, "invoice_encode called!\n"); abort(); } { 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 */ /* Generated stub for invoice_path_id */
u8 *invoice_path_id(const tal_t *ctx UNNEEDED, u8 *invoice_path_id(const tal_t *ctx UNNEEDED,
const struct secret *base_secret UNNEEDED, const struct secret *base_secret UNNEEDED,

View File

@@ -1192,10 +1192,10 @@ static char *fetch_out_desc_invstr(const tal_t *ctx, const char *buf,
return NULL; return NULL;
} }
if (bolt12->description) if (bolt12->offer_description)
desc = tal_strndup(ctx, desc = tal_strndup(ctx,
cast_signed(char *, bolt12->description), cast_signed(char *, bolt12->offer_description),
tal_bytelen(bolt12->description)); tal_bytelen(bolt12->offer_description));
else else
desc = NULL; desc = NULL;
} else } else

View File

@@ -59,37 +59,6 @@ static struct sent *find_sent_by_secret(const struct secret *pathsecret)
return NULL; 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 */ /* Hack to suppress warnings when we finish a different command */
static void discard_result(struct command_result *ret) 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); 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, static struct command_result *handle_invreq_response(struct command *cmd,
struct sent *sent, struct sent *sent,
const char *buf, const char *buf,
const jsmntok_t *om) const jsmntok_t *om)
{ {
const u8 *invbin; const u8 *invbin, *cursor;
const jsmntok_t *invtok; const jsmntok_t *invtok;
size_t len; size_t len;
struct tlv_invoice *inv; 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); invbin = json_tok_bin_from_hex(cmd, buf, invtok);
cursor = invbin;
len = tal_bytelen(invbin); len = tal_bytelen(invbin);
inv = fromwire_tlv_invoice(cmd, &invbin, &len); inv = fromwire_tlv_invoice(cmd, &cursor, &len);
if (!inv) { if (!inv) {
badfield = "invoice"; badfield = "invoice";
goto badinv; goto badinv;
@@ -202,88 +193,64 @@ static struct command_result *handle_invreq_response(struct command *cmd,
#endif /* DEVELOPER */ #endif /* DEVELOPER */
/* BOLT-offers #12: /* 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)) { if (!invoice_matches_request(cmd, invbin, sent->invreq)) {
badfield = "node_id"; 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; goto badinv;
} }
/* BOLT-offers #12: /* BOLT-offers #12:
* - MUST reject the invoice if `signature` is not a valid signature * - 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); merkle_tlv(inv->fields, &merkle);
sighash_from_merkle("invoice", "signature", &merkle, &sighash); sighash_from_merkle("invoice", "signature", &merkle, &sighash);
if (!inv->signature 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"; badfield = "signature";
goto badinv; goto badinv;
} }
/* BOLT-offers #12: /* 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) { if (!inv->invoice_amount) {
badfield = "amount"; badfield = "invoice_amount";
goto badinv; goto badinv;
} }
/* BOLT-offers #12: /* FIXME-OFFERS: Examine fields in inv directly! */
* - 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;
/* Get the amount we expected: firstly, if that's what we sent, /* Get the amount we expected: firstly, if that's what we sent,
* secondly, if specified in the invoice. */ * secondly, if specified in the invoice. */
if (sent->invreq->amount) { if (sent->invreq->invreq_amount) {
expected_amount = tal_dup(tmpctx, u64, sent->invreq->amount); expected_amount = tal_dup(tmpctx, u64, sent->invreq->invreq_amount);
} else if (sent->offer->amount && !sent->offer->currency) { } else if (sent->offer->offer_amount && !sent->offer->offer_currency) {
expected_amount = tal(tmpctx, u64); expected_amount = tal(tmpctx, u64);
*expected_amount = *sent->offer->amount; *expected_amount = *sent->offer->offer_amount;
if (sent->invreq->quantity) { if (sent->invreq->invreq_quantity) {
/* We should never have sent this! */ /* We should never have sent this! */
if (mul_overflows_u64(*expected_amount, if (mul_overflows_u64(*expected_amount,
*sent->invreq->quantity)) { *sent->invreq->invreq_quantity)) {
badfield = "quantity overflow"; badfield = "quantity overflow";
goto badinv; goto badinv;
} }
*expected_amount *= *sent->invreq->quantity; *expected_amount *= *sent->invreq->invreq_quantity;
} }
} else } else
expected_amount = NULL; expected_amount = NULL;
@@ -292,97 +259,56 @@ static struct command_result *handle_invreq_response(struct command *cmd,
* - if the offer contained `recurrence`: * - if the offer contained `recurrence`:
* - MUST reject the invoice if `recurrence_basetime` is not set. * - 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"; badfield = "recurrence_basetime";
goto badinv; 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); out = jsonrpc_stream_success(sent->cmd);
json_add_string(out, "invoice", invoice_encode(tmpctx, inv)); json_add_string(out, "invoice", invoice_encode(tmpctx, inv));
json_object_start(out, "changes"); 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: /* BOLT-offers #12:
* - SHOULD confirm authorization if `msat` is not within the amount * - SHOULD confirm authorization if `msat` is not within the amount
* range authorized. * range authorized.
*/ */
/* We always tell them this unless it's trivial to calc and /* We always tell them this unless it's trivial to calc and
* exactly as expected. */ * exactly as expected. */
if (!expected_amount || *inv->amount != *expected_amount) { if (!expected_amount || *inv->invoice_amount != *expected_amount) {
if (deprecated_apis)
json_add_amount_msat_only(out, "msat",
amount_msat(*inv->amount));
json_add_amount_msat_only(out, "amount_msat", json_add_amount_msat_only(out, "amount_msat",
amount_msat(*inv->amount)); amount_msat(*inv->invoice_amount));
} }
json_object_end(out); json_object_end(out);
/* We tell them about next period at this point, if any. */ /* 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 next_counter, next_period_idx;
u64 paywindow_start, paywindow_end; u64 paywindow_start, paywindow_end;
next_counter = *sent->invreq->recurrence_counter + 1; next_counter = *sent->invreq->invreq_recurrence_counter + 1;
if (sent->invreq->recurrence_start) if (sent->invreq->invreq_recurrence_start)
next_period_idx = *sent->invreq->recurrence_start next_period_idx = *sent->invreq->invreq_recurrence_start
+ next_counter; + next_counter;
else else
next_period_idx = next_counter; next_period_idx = next_counter;
/* If this was the last, don't tell them about a next! */ /* If this was the last, don't tell them about a next! */
if (!sent->offer->recurrence_limit if (!sent->offer->offer_recurrence_limit
|| next_period_idx <= *sent->offer->recurrence_limit) { || next_period_idx <= *sent->offer->offer_recurrence_limit) {
json_object_start(out, "next_period"); json_object_start(out, "next_period");
json_add_u64(out, "counter", next_counter); json_add_u64(out, "counter", next_counter);
json_add_u64(out, "starttime", json_add_u64(out, "starttime",
offer_period_start(*inv->recurrence_basetime, offer_period_start(*inv->invoice_recurrence_basetime,
next_period_idx, next_period_idx,
sent->offer->recurrence)); sent->offer->offer_recurrence));
json_add_u64(out, "endtime", json_add_u64(out, "endtime",
offer_period_start(*inv->recurrence_basetime, offer_period_start(*inv->invoice_recurrence_basetime,
next_period_idx + 1, next_period_idx + 1,
sent->offer->recurrence) - 1); sent->offer->offer_recurrence) - 1);
offer_period_paywindow(sent->offer->recurrence, offer_period_paywindow(sent->offer->offer_recurrence,
sent->offer->recurrence_paywindow, sent->offer->offer_recurrence_paywindow,
sent->offer->recurrence_base, sent->offer->offer_recurrence_base,
*inv->recurrence_basetime, *inv->invoice_recurrence_basetime,
next_period_idx, next_period_idx,
&paywindow_start, &paywindow_end); &paywindow_start, &paywindow_end);
json_add_u64(out, "paywindow_start", paywindow_start); 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) struct tlv_offer **offer)
{ {
char *fail; 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, *offer = offer_decode(cmd, buffer + tok->start, tok->end - tok->start,
plugin_feature_set(cmd->plugin), chainparams, plugin_feature_set(cmd->plugin), chainparams,
&fail); &fail);
@@ -520,19 +436,61 @@ static struct command_result *param_offer(struct command *cmd,
tal_fmt(cmd, tal_fmt(cmd,
"Unparsable offer: %s", "Unparsable offer: %s",
fail)); fail));
/* BOLT-offers #12: /* BOLT-offers #12:
* * A reader of an offer:
* - if `node_id` or `description` is not set: * - 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. * - 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, return command_fail_badparam(cmd, name, buffer, tok,
"Offer does not contain a node_id"); "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, return command_fail_badparam(cmd, name, buffer, tok,
"Offer does not contain a description"); "Offer for wrong chains");
}
return NULL; 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 /* Now that's given us the previous base, check this is an OK time
* to request an invoice. */ * to request an invoice. */
if (sent->invreq->recurrence_counter) { if (sent->invreq->invreq_recurrence_counter) {
u64 *base; u64 *base;
const jsmntok_t *pbtok; const jsmntok_t *pbtok;
u64 period_idx = *sent->invreq->recurrence_counter; u64 period_idx = *sent->invreq->invreq_recurrence_counter;
if (sent->invreq->recurrence_start) if (sent->invreq->invreq_recurrence_start)
period_idx += *sent->invreq->recurrence_start; period_idx += *sent->invreq->invreq_recurrence_start;
/* BOLT-offers-recurrence #12: /* BOLT-offers-recurrence #12:
* - if the offer contained `recurrence_limit`: * - if the offer contained `recurrence_limit`:
* - MUST NOT send an `invoice_request` for a period greater * - MUST NOT send an `invoice_request` for a period greater
* than `max_period` * than `max_period`
*/ */
if (sent->offer->recurrence_limit if (sent->offer->offer_recurrence_limit
&& period_idx > *sent->offer->recurrence_limit) && period_idx > *sent->offer->offer_recurrence_limit)
return command_fail(cmd, LIGHTNINGD, return command_fail(cmd, LIGHTNINGD,
"Can't send invreq for period %" "Can't send invreq for period %"
PRIu64" (limit %u)", PRIu64" (limit %u)",
period_idx, period_idx,
*sent->offer->recurrence_limit); *sent->offer->offer_recurrence_limit);
/* BOLT-offers-recurrence #12: /* BOLT-offers-recurrence #12:
* - SHOULD NOT send an `invoice_request` for a period which has * - 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) { if (pbtok) {
base = tal(tmpctx, u64); base = tal(tmpctx, u64);
json_to_u64(buf, pbtok, base); json_to_u64(buf, pbtok, base);
} else if (sent->offer->recurrence_base) } else if (sent->offer->offer_recurrence_base)
base = &sent->offer->recurrence_base->basetime; base = &sent->offer->offer_recurrence_base->basetime;
else { else {
/* happens with *recurrence_base == 0 */ /* happens with *recurrence_base == 0 */
assert(*sent->invreq->recurrence_counter == 0); assert(*sent->invreq->invreq_recurrence_counter == 0);
base = NULL; base = NULL;
} }
if (base) { if (base) {
u64 period_start, period_end, now = time_now().ts.tv_sec; u64 period_start, period_end, now = time_now().ts.tv_sec;
offer_period_paywindow(sent->offer->recurrence, offer_period_paywindow(sent->offer->offer_recurrence,
sent->offer->recurrence_paywindow, sent->offer->offer_recurrence_paywindow,
sent->offer->recurrence_base, sent->offer->offer_recurrence_base,
*base, period_idx, *base, period_idx,
&period_start, &period_end); &period_start, &period_end);
if (now < period_start) 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->path = path_to_node(sent, cmd->plugin,
sent->offer->node_id); sent->offer->offer_node_id);
if (!sent->path) if (!sent->path)
return connect_direct(cmd, sent->offer->node_id, return connect_direct(cmd, sent->offer->offer_node_id,
sendinvreq_after_connect, sent); sendinvreq_after_connect, sent);
return sendinvreq_after_connect(cmd, NULL, NULL, 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) if (secp256k1_keypair_create(secp256k1_ctx, &kp, payer_secret->data) != 1)
return command_fail(cmd, LIGHTNINGD, "Bad payer_secret"); 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! */ /* Docs say this only happens if arguments are invalid! */
if (secp256k1_keypair_pub(secp256k1_ctx, if (secp256k1_keypair_pub(secp256k1_ctx,
&invreq->payer_key->pubkey, &invreq->invreq_payer_id->pubkey,
&kp) != 1) &kp) != 1)
plugin_err(cmd->plugin, plugin_err(cmd->plugin,
"secp256k1_keypair_pub failed on %s?", "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->path = path_to_node(sent, cmd->plugin,
sent->offer->node_id); sent->offer->offer_node_id);
if (!sent->path) if (!sent->path)
return connect_direct(cmd, sent->offer->node_id, return connect_direct(cmd, sent->offer->offer_node_id,
sendinvreq_after_connect, sent); sendinvreq_after_connect, sent);
return sendinvreq_after_connect(cmd, NULL, NULL, 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 sent *sent = tal(cmd, struct sent);
struct secret *payer_secret = NULL; struct secret *payer_secret = NULL;
u32 *timeout; u32 *timeout;
u64 *quantity;
u32 *recurrence_counter, *recurrence_start;
invreq = tlv_invoice_request_new(sent);
if (!param(cmd, buffer, params, if (!param(cmd, buffer, params,
p_req("offer", param_offer, &sent->offer), p_req("offer", param_offer, &sent->offer),
p_opt("amount_msat|msatoshi", param_msat, &msat), p_opt("amount_msat|msatoshi", param_msat, &msat),
p_opt("quantity", param_u64, &invreq->quantity), p_opt("quantity", param_u64, &quantity),
p_opt("recurrence_counter", param_number, p_opt("recurrence_counter", param_number, &recurrence_counter),
&invreq->recurrence_counter), p_opt("recurrence_start", param_number, &recurrence_start),
p_opt("recurrence_start", param_number,
&invreq->recurrence_start),
p_opt("recurrence_label", param_string, &rec_label), p_opt("recurrence_label", param_string, &rec_label),
p_opt_def("timeout", param_number, &timeout, 60), p_opt_def("timeout", param_number, &timeout, 60),
p_opt("payer_note", param_string, &payer_note), 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; 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: /* BOLT-offers #12:
* - SHOULD not respond to an offer if the current time is after * - SHOULD not respond to an offer if the current time is after
* `absolute_expiry`. * `absolute_expiry`.
*/ */
if (sent->offer->absolute_expiry if (sent->offer->offer_absolute_expiry
&& time_now().ts.tv_sec > *sent->offer->absolute_expiry) && time_now().ts.tv_sec > *sent->offer->offer_absolute_expiry)
return command_fail(cmd, OFFER_EXPIRED, "Offer expired"); return command_fail(cmd, OFFER_EXPIRED, "Offer expired");
/* BOLT-offers-recurrence #12: /* BOLT-offers #12:
* - if the offer did not specify `amount`: * The writer:
* - MUST specify `amount`.`msat` in multiples of the minimum * - if it is responding to an offer:
* lightning-payable unit (e.g. milli-satoshis for bitcoin) for * - MUST copy all fields from the offer (including unknown fields).
* `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 (sent->offer->amount) { 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 `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->offer_amount) {
/* FIXME: Check after quantity? */ /* FIXME: Check after quantity? */
if (msat) { if (msat) {
invreq->amount = tal_dup(invreq, u64, invreq->invreq_amount = tal_dup(invreq, u64,
&msat->millisatoshis); /* Raw: tu64 */ &msat->millisatoshis); /* Raw: tu64 */
} }
} else { } else {
if (!msat) if (!msat)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"msatoshi parameter required"); "msatoshi parameter required");
invreq->amount = tal_dup(invreq, u64, invreq->invreq_amount = tal_dup(invreq, u64,
&msat->millisatoshis); /* Raw: tu64 */ &msat->millisatoshis); /* Raw: tu64 */
} }
/* FIXME-OFFERS: Examine fields in inv directly! */
/* BOLT-offers #12: /* BOLT-offers #12:
* - if the offer had a `quantity_min` or `quantity_max` field: * - if `offer_quantity_max` is present:
* - MUST set `quantity` * - MUST set `invreq_quantity`
* - MUST set it within that (inclusive) range. * - if `offer_quantity_max` is non-zero:
* - otherwise: * - MUST set `invreq_quantity` less than or equal to
* - MUST NOT set `quantity` * `offer_quantity_max`.
*/ */
if (sent->offer->quantity_min || sent->offer->quantity_max) { if (sent->offer->offer_quantity_max) {
if (!invreq->quantity) if (!invreq->invreq_quantity)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"quantity parameter required"); "quantity parameter required");
if (sent->offer->quantity_min if (*sent->offer->offer_quantity_max
&& *invreq->quantity < *sent->offer->quantity_min) && *invreq->invreq_quantity > *sent->offer->offer_quantity_max)
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)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"quantity must be <= %"PRIu64, "quantity must be <= %"PRIu64,
*sent->offer->quantity_max); *sent->offer->offer_quantity_max);
} else { } else {
if (invreq->quantity) if (invreq->invreq_quantity)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"quantity parameter unnecessary"); "quantity parameter unnecessary");
} }
@@ -1112,7 +1061,7 @@ static struct command_result *json_fetchinvoice(struct command *cmd,
/* BOLT-offers-recurrence #12: /* BOLT-offers-recurrence #12:
* - if the offer contained `recurrence`: * - if the offer contained `recurrence`:
*/ */
if (sent->offer->recurrence) { if (sent->offer->offer_recurrence) {
/* BOLT-offers-recurrence #12: /* BOLT-offers-recurrence #12:
* - for the initial request: * - 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 * - MUST set `recurrence_counter` `counter` to one greater
* than the highest-paid invoice. * than the highest-paid invoice.
*/ */
if (!invreq->recurrence_counter) if (!invreq->invreq_recurrence_counter)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"needs recurrence_counter"); "needs recurrence_counter");
@@ -1136,13 +1085,13 @@ static struct command_result *json_fetchinvoice(struct command *cmd,
* - otherwise: * - otherwise:
* - MUST NOT include `recurrence_start` * - MUST NOT include `recurrence_start`
*/ */
if (sent->offer->recurrence_base if (sent->offer->offer_recurrence_base
&& sent->offer->recurrence_base->start_any_period) { && sent->offer->offer_recurrence_base->start_any_period) {
if (!invreq->recurrence_start) if (!invreq->invreq_recurrence_start)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"needs recurrence_start"); "needs recurrence_start");
} else { } else {
if (invreq->recurrence_start) if (invreq->invreq_recurrence_start)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"unnecessary recurrence_start"); "unnecessary recurrence_start");
} }
@@ -1158,33 +1107,40 @@ static struct command_result *json_fetchinvoice(struct command *cmd,
* - MUST NOT set `recurrence_counter`. * - MUST NOT set `recurrence_counter`.
* - MUST NOT set `recurrence_start` * - MUST NOT set `recurrence_start`
*/ */
if (invreq->recurrence_counter) if (invreq->invreq_recurrence_counter)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"unnecessary recurrence_counter"); "unnecessary recurrence_counter");
if (invreq->recurrence_start) if (invreq->invreq_recurrence_start)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"unnecessary recurrence_start"); "unnecessary recurrence_start");
} }
/* BOLT-offers #12: /* BOLT-offers #12:
* *
* - if the chain for the invoice is not solely bitcoin: * - if `offer_chains` is set:
* - MUST specify `chains` the offer is valid for. * - MUST set `invreq_chain` to one of `offer_chains` unless that
* chain is bitcoin, in which case it MAY omit `invreq_chain`.
* - otherwise: * - 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")) { if (!streq(chainparams->network_name, "bitcoin")) {
invreq->chain = tal_dup(invreq, struct bitcoin_blkid, invreq->invreq_chain = tal_dup(invreq, struct bitcoin_blkid,
&chainparams->genesis_blockhash); &chainparams->genesis_blockhash);
} }
invreq->features /* BOLT-offers #12:
= plugin_feature_set(cmd->plugin)->bits[BOLT11_FEATURE]; * - 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! */ /* invreq->payer_note is not a nul-terminated string! */
if (payer_note) if (payer_note)
invreq->payer_note = tal_dup_arr(invreq, utf8, invreq->invreq_payer_note = tal_dup_arr(invreq, utf8,
payer_note, strlen(payer_note), payer_note,
strlen(payer_note),
0); 0);
/* They can provide a secret, and we don't assume it's our job /* They can provide a secret, and we don't assume it's our job

View File

@@ -317,100 +317,97 @@ static void json_add_offer(struct json_stream *js, const struct tlv_offer *offer
struct sha256 offer_id; struct sha256 offer_id;
bool valid = true; 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); json_add_sha256(js, "offer_id", &offer_id);
if (offer->chains) if (offer->offer_chains)
json_add_chains(js, offer->chains); json_add_chains(js, offer->offer_chains);
if (offer->currency) { if (offer->offer_currency) {
const struct iso4217_name_and_divisor *iso4217; const struct iso4217_name_and_divisor *iso4217;
json_add_stringn(js, "currency", json_add_stringn(js, "currency",
offer->currency, tal_bytelen(offer->currency)); offer->offer_currency,
if (offer->amount) tal_bytelen(offer->offer_currency));
json_add_u64(js, "amount", *offer->amount); if (offer->offer_amount)
iso4217 = find_iso4217(offer->currency, json_add_u64(js, "amount", *offer->offer_amount);
tal_bytelen(offer->currency)); iso4217 = find_iso4217(offer->offer_currency,
tal_bytelen(offer->offer_currency));
if (iso4217) if (iso4217)
json_add_num(js, "minor_unit", iso4217->minor_unit); json_add_num(js, "minor_unit", iso4217->minor_unit);
else else
json_add_string(js, "warning_offer_unknown_currency", json_add_string(js, "warning_offer_unknown_currency",
"unknown currency code"); "unknown currency code");
} else if (offer->amount) } else if (offer->offer_amount)
json_add_amount_msat_only(js, "amount_msat", json_add_amount_msat_only(js, "amount_msat",
amount_msat(*offer->amount)); amount_msat(*offer->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);
/* BOLT-offers #12: /* BOLT-offers #12:
* A reader of an offer: * A reader of an offer:
*... *...
* - if `node_id` or `description` is not set: * - if `offer_description` is not set:
* - MUST NOT respond to the offer. * - MUST NOT respond to the offer.
*/ */
if (offer->description) if (offer->offer_description)
json_add_stringn(js, "description", json_add_stringn(js, "description",
offer->description, offer->offer_description,
tal_bytelen(offer->description)); tal_bytelen(offer->offer_description));
else { else {
json_add_string(js, "warning_offer_missing_description", json_add_string(js, "warning_offer_missing_description",
"offers without a description are invalid"); "offers without a description are invalid");
valid = false; valid = false;
} }
if (offer->issuer) if (offer->offer_issuer)
json_add_stringn(js, "issuer", offer->issuer, json_add_stringn(js, "issuer", offer->offer_issuer,
tal_bytelen(offer->issuer)); tal_bytelen(offer->offer_issuer));
if (offer->features) if (offer->offer_features)
json_add_hex_talarr(js, "features", offer->features); json_add_hex_talarr(js, "features", offer->offer_features);
if (offer->absolute_expiry) if (offer->offer_absolute_expiry)
json_add_u64(js, "absolute_expiry", json_add_u64(js, "absolute_expiry",
*offer->absolute_expiry); *offer->offer_absolute_expiry);
if (offer->paths) if (offer->offer_paths)
valid &= json_add_blinded_paths(js, offer->paths, NULL); valid &= json_add_blinded_paths(js, offer->offer_paths, NULL);
if (offer->quantity_min) if (offer->offer_quantity_max)
json_add_u64(js, "quantity_min", *offer->quantity_min); json_add_u64(js, "quantity_max", *offer->offer_quantity_max);
if (offer->quantity_max) if (offer->offer_recurrence) {
json_add_u64(js, "quantity_max", *offer->quantity_max);
if (offer->recurrence) {
const char *name; const char *name;
json_object_start(js, "recurrence"); json_object_start(js, "recurrence");
json_add_num(js, "time_unit", offer->recurrence->time_unit); json_add_num(js, "time_unit", offer->offer_recurrence->time_unit);
name = recurrence_time_unit_name(offer->recurrence->time_unit); name = recurrence_time_unit_name(offer->offer_recurrence->time_unit);
if (name) if (name)
json_add_string(js, "time_unit_name", name); json_add_string(js, "time_unit_name", name);
json_add_num(js, "period", offer->recurrence->period); json_add_num(js, "period", offer->offer_recurrence->period);
if (offer->recurrence_base) { if (offer->offer_recurrence_base) {
json_add_u64(js, "basetime", json_add_u64(js, "basetime",
offer->recurrence_base->basetime); offer->offer_recurrence_base->basetime);
if (offer->recurrence_base->start_any_period) if (offer->offer_recurrence_base->start_any_period)
json_add_bool(js, "start_any_period", true); json_add_bool(js, "start_any_period", true);
} }
if (offer->recurrence_limit) if (offer->offer_recurrence_limit)
json_add_u32(js, "limit", *offer->recurrence_limit); json_add_u32(js, "limit", *offer->offer_recurrence_limit);
if (offer->recurrence_paywindow) { if (offer->offer_recurrence_paywindow) {
json_object_start(js, "paywindow"); json_object_start(js, "paywindow");
json_add_u32(js, "seconds_before", json_add_u32(js, "seconds_before",
offer->recurrence_paywindow->seconds_before); offer->offer_recurrence_paywindow->seconds_before);
json_add_u32(js, "seconds_after", json_add_u32(js, "seconds_after",
offer->recurrence_paywindow->seconds_after); offer->offer_recurrence_paywindow->seconds_after);
if (offer->recurrence_paywindow->proportional_amount) if (offer->offer_recurrence_paywindow->proportional_amount)
json_add_bool(js, "proportional_amount", true); json_add_bool(js, "proportional_amount", true);
json_object_end(js); json_object_end(js);
} }
json_object_end(js); json_object_end(js);
} }
if (offer->node_id) /* BOLT-offers #12:
json_add_pubkey(js, "node_id", offer->node_id); * - 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 else
valid = false; 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); json_add_bool(js, "valid", valid);
} }
@@ -491,41 +488,83 @@ static void json_add_b12_invoice(struct json_stream *js,
{ {
bool valid = true; bool valid = true;
if (invoice->chain) /* If there's an offer_node_id, then there's an offer. */
json_add_sha256(js, "chain", &invoice->chain->shad.sha); if (invoice->offer_node_id) {
if (invoice->offer_id) struct sha256 offer_id;
json_add_sha256(js, "offer_id", invoice->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: /* 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", json_add_amount_msat_only(js, "amount_msat",
amount_msat(*invoice->amount)); amount_msat(*invoice->invoice_amount));
else { else {
json_add_string(js, "warning_invoice_missing_amount", json_add_string(js, "warning_invoice_missing_amount",
"invoices without an amount are invalid"); "invoices without an amount are invalid");
valid = false; 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: /* 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) if (invoice->offer_description)
json_add_stringn(js, "description", invoice->description, json_add_stringn(js, "description", invoice->offer_description,
tal_bytelen(invoice->description)); tal_bytelen(invoice->offer_description));
else { else {
json_add_string(js, "warning_invoice_missing_description", json_add_string(js, "warning_invoice_missing_description",
"invoices without a description are invalid"); "invoices without a description are invalid");
valid = false; valid = false;
} }
if (invoice->issuer) if (invoice->invoice_created_at) {
json_add_stringn(js, "issuer", invoice->issuer, json_add_u64(js, "created_at", *invoice->invoice_created_at);
tal_bytelen(invoice->issuer)); } else {
if (invoice->features) json_add_string(js, "warning_invoice_missing_created_at",
json_add_hex_talarr(js, "features", invoice->features); "invoices without created_at are invalid");
if (invoice->paths) { 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: /* BOLT-offers #12:
* - if `blinded_path` is present: * - if `blinded_path` is present:
* - MUST reject the invoice if `blinded_payinfo` is not * - 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` * contain exactly as many `payinfo` as total `onionmsg_path`
* in `blinded_path`. * in `blinded_path`.
*/ */
if (!invoice->blindedpay) { if (!invoice->invoice_blindedpay) {
json_add_string(js, "warning_invoice_missing_blinded_payinfo", json_add_string(js, "warning_invoice_missing_blinded_payinfo",
"invoices with blinded_path without blinded_payinfo are invalid"); "invoices with blinded_path without blinded_payinfo are invalid");
valid = false; 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->invreq_quantity)
if (invoice->send_invoice) json_add_u64(js, "quantity", *invoice->invreq_quantity);
json_add_bool(js, "send_invoice", true); if (invoice->invreq_recurrence_counter) {
if (invoice->refund_for)
json_add_sha256(js, "refund_for", invoice->refund_for);
if (invoice->recurrence_counter) {
json_add_u32(js, "recurrence_counter", json_add_u32(js, "recurrence_counter",
*invoice->recurrence_counter); *invoice->invreq_recurrence_counter);
if (invoice->recurrence_start) if (invoice->invreq_recurrence_start)
json_add_u32(js, "recurrence_start", json_add_u32(js, "recurrence_start",
*invoice->recurrence_start); *invoice->invreq_recurrence_start);
/* BOLT-offers-recurrence #12: /* BOLT-offers-recurrence #12:
* - if the offer contained `recurrence`: * - if the offer contained `recurrence`:
* - MUST reject the invoice if `recurrence_basetime` is not * - MUST reject the invoice if `recurrence_basetime` is not
* set. * set.
*/ */
if (invoice->recurrence_basetime) if (invoice->invoice_recurrence_basetime)
json_add_u64(js, "recurrence_basetime", json_add_u64(js, "recurrence_basetime",
*invoice->recurrence_basetime); *invoice->invoice_recurrence_basetime);
else { else {
json_add_string(js, "warning_invoice_missing_recurrence_basetime", json_add_string(js, "warning_invoice_missing_recurrence_basetime",
"recurring invoices without a recurrence_basetime are invalid"); "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) if (invoice->invreq_metadata)
json_add_pubkey(js, "payer_key", invoice->payer_key); json_add_hex_talarr(js, "invreq_metadata",
if (invoice->payer_info) invoice->invreq_metadata);
json_add_hex_talarr(js, "payer_info", invoice->payer_info); if (invoice->invreq_payer_note)
if (invoice->payer_note) json_add_stringn(js, "payer_note", invoice->invreq_payer_note,
json_add_stringn(js, "payer_note", invoice->payer_note, tal_bytelen(invoice->invreq_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;
}
/* BOLT-offers #12: /* BOLT-offers #12:
* *
* - if the expiry for accepting payment is not 7200 seconds after * - if `invoice_relative_expiry` is present:
* `created_at`: * - MUST reject the invoice if the current time since 1970-01-01 UTC
* - MUST set `relative_expiry` * 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) if (invoice->invoice_relative_expiry)
json_add_u32(js, "relative_expiry", *invoice->relative_expiry); json_add_u32(js, "relative_expiry", *invoice->invoice_relative_expiry);
else else
json_add_u32(js, "relative_expiry", 7200); json_add_u32(js, "relative_expiry", 7200);
/* BOLT-offers #12: if (invoice->invoice_fallbacks)
* - 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)
valid &= json_add_fallbacks(js, valid &= json_add_fallbacks(js,
invoice->chain, invoice->invreq_chain,
invoice->fallbacks); invoice->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_decode checked these */ /* 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_bip340sig(js, "signature", invoice->signature);
json_add_bool(js, "valid", valid); json_add_bool(js, "valid", valid);
@@ -668,8 +647,17 @@ static void json_add_invoice_request(struct json_stream *js,
{ {
bool valid = true; bool valid = true;
if (invreq->chain) /* If there's an offer_node_id, then there's an offer. */
json_add_sha256(js, "chain", &invreq->chain->shad.sha); 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: /* BOLT-offers #12:
* - MUST fail the request if `payer_key` is not present. * - 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 `features` contains unknown even bits.
* - MUST fail the request if `offer_id` is not present. * - MUST fail the request if `offer_id` is not present.
*/ */
if (invreq->offer_id) if (invreq->invreq_amount)
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)
json_add_amount_msat_only(js, "amount_msat", json_add_amount_msat_only(js, "amount_msat",
amount_msat(*invreq->amount)); amount_msat(*invreq->invreq_amount));
if (invreq->features) if (invreq->invreq_features)
json_add_hex_talarr(js, "features", invreq->features); json_add_hex_talarr(js, "features", invreq->invreq_features);
if (invreq->quantity) if (invreq->invreq_quantity)
json_add_u64(js, "quantity", *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", json_add_u32(js, "recurrence_counter",
*invreq->recurrence_counter); *invreq->invreq_recurrence_counter);
if (invreq->recurrence_start) if (invreq->invreq_recurrence_start)
json_add_u32(js, "recurrence_start", json_add_u32(js, "recurrence_start",
*invreq->recurrence_start); *invreq->invreq_recurrence_start);
if (invreq->payer_key) /* BOLT-offers #12:
json_add_pubkey(js, "payer_key", invreq->payer_key); * - 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 { else {
json_add_string(js, "warning_invoice_request_missing_payer_key", json_add_string(js, "warning_invoice_request_missing_payer_key",
"invoice_request requires payer_key"); "invoice_request requires payer_key");
valid = false; valid = false;
} }
if (invreq->payer_info) if (invreq->invreq_metadata)
json_add_hex_talarr(js, "payer_info", invreq->payer_info); json_add_hex_talarr(js, "invreq_metadata", invreq->invreq_metadata);
if (invreq->payer_note) else {
json_add_stringn(js, "payer_note", invreq->payer_note, json_add_string(js, "warning_invoice_request_missing_invreq_metadata",
tal_bytelen(invreq->payer_note)); "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: /* BOLT-offers #12:
* - MUST fail the request if there is no `signature` field. * - MUST fail the request if `signature` is not correct as detailed
* - MUST fail the request if `signature` is not correct. * in [Signature Calculation](#signature-calculation) using the
* `invreq_payer_id`.
*/ */
if (invreq->signature) { if (invreq->signature) {
if (invreq->payer_key if (invreq->invreq_payer_id
&& !bolt12_check_signature(invreq->fields, && !bolt12_check_signature(invreq->fields,
"invoice_request", "invoice_request",
"signature", "signature",
invreq->payer_key, invreq->invreq_payer_id,
invreq->signature)) { invreq->signature)) {
json_add_string(js, "warning_invoice_request_invalid_signature", json_add_string(js, "warning_invoice_request_invalid_signature",
"Bad signature"); "Bad signature");

View File

@@ -22,6 +22,8 @@ struct invreq {
/* The offer, once we've looked it up. */ /* The offer, once we've looked it up. */
struct tlv_offer *offer; struct tlv_offer *offer;
/* The offer id */
struct sha256 offer_id;
/* The invoice we're preparing (can require additional lookups) */ /* The invoice we're preparing (can require additional lookups) */
struct tlv_invoice *inv; struct tlv_invoice *inv;
@@ -40,14 +42,10 @@ fail_invreq_level(struct command *cmd,
struct tlv_onionmsg_tlv *payload; struct tlv_onionmsg_tlv *payload;
struct tlv_invoice_error *err; struct tlv_invoice_error *err;
full_fmt = tal_fmt(tmpctx, "Failed invoice_request"); full_fmt = tal_fmt(tmpctx, "Failed invreq");
if (invreq->invreq) { if (invreq->invreq) {
tal_append_fmt(&full_fmt, " %s", tal_append_fmt(&full_fmt, " %s",
invrequest_encode(tmpctx, invreq->invreq)); 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); 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) 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. */ /* Don't give them a 0 second invoice, even if it's true. */
if (last_pay <= *inv->created_at) if (last_pay <= *inv->invoice_created_at)
*inv->relative_expiry = 1; *inv->invoice_relative_expiry = 1;
else 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! */ /* 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_string(req->js, "invstring", invoice_encode(tmpctx, ir->inv));
json_add_preimage(req->js, "preimage", &ir->preimage); json_add_preimage(req->js, "preimage", &ir->preimage);
json_add_label(req->js, ir->inv->offer_id, ir->inv->payer_key, json_add_label(req->js, &ir->offer_id, ir->inv->invreq_payer_id,
ir->inv->recurrence_counter ir->inv->invreq_recurrence_counter
? *ir->inv->recurrence_counter : 0); ? *ir->inv->invreq_recurrence_counter : 0);
return send_outreq(cmd->plugin, req); 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)) if (!feature_offered(features, OPT_ROUTE_BLINDING))
continue; 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; continue;
/* Only pick a private one if no public candidates. */ /* 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. */ /* Note: since we don't make one, createinvoice adds a dummy. */
plugin_log(cmd->plugin, LOG_UNUSUAL, plugin_log(cmd->plugin, LOG_UNUSUAL,
"No incoming channel for %s, so no blinded path", "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 { } else {
struct privkey blinding; struct privkey blinding;
struct tlv_encrypted_data_tlv_payment_relay relay; 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_base_msat = best->feebase;
relay.fee_proportional_millionths = best->feeppm; 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. */ /* Give them 6 blocks, plus one per 10 minutes until expiry. */
if (ir->inv->relative_expiry) if (ir->inv->invoice_relative_expiry)
base = blockheight + 6 + *ir->inv->relative_expiry / 600; base = blockheight + 6 + *ir->inv->invoice_relative_expiry / 600;
else else
base = blockheight + 6 + 7200 / 600; base = blockheight + 6 + 7200 / 600;
constraints.max_cltv_expiry = base + best->cltv + cltv_final; 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)); randombytes_buf(&blinding, sizeof(blinding));
ir->inv->paths = tal_arr(ir->inv, struct blinded_path *, 1); ir->inv->invoice_paths = tal_arr(ir->inv, struct blinded_path *, 1);
ir->inv->paths[0] = tal(ir->inv->paths, struct blinded_path); ir->inv->invoice_paths[0] = tal(ir->inv->invoice_paths, struct blinded_path);
ir->inv->paths[0]->first_node_id = best->id; ir->inv->invoice_paths[0]->first_node_id = best->id;
if (!pubkey_from_privkey(&blinding, if (!pubkey_from_privkey(&blinding,
&ir->inv->paths[0]->blinding)) &ir->inv->invoice_paths[0]->blinding))
abort(); abort();
hops = tal_arr(ir->inv->paths[0], struct onionmsg_hop *, 2); hops = tal_arr(ir->inv->invoice_paths[0], struct onionmsg_hop *, 2);
ir->inv->paths[0]->path = hops; ir->inv->invoice_paths[0]->path = hops;
/* First hop is the peer */ /* First hop is the peer */
hops[0] = tal(hops, struct onionmsg_hop); hops[0] = tal(hops, struct onionmsg_hop);
@@ -395,18 +400,18 @@ static struct command_result *listincoming_done(struct command *cmd,
&id, &id,
NULL, NULL, NULL, NULL, NULL, NULL,
invoice_path_id(tmpctx, &invoicesecret_base, invoice_path_id(tmpctx, &invoicesecret_base,
ir->inv->payment_hash), ir->inv->invoice_payment_hash),
&hops[1]->blinded_node_id); &hops[1]->blinded_node_id);
/* FIXME: This should be a "normal" feerate and range. */ /* FIXME: This should be a "normal" feerate and range. */
ir->inv->blindedpay = tal_arr(ir->inv, struct blinded_payinfo *, 1); ir->inv->invoice_blindedpay = tal_arr(ir->inv, struct blinded_payinfo *, 1);
ir->inv->blindedpay[0] = tal(ir->inv->blindedpay, struct blinded_payinfo); ir->inv->invoice_blindedpay[0] = tal(ir->inv->invoice_blindedpay, struct blinded_payinfo);
ir->inv->blindedpay[0]->fee_base_msat = best->feebase; ir->inv->invoice_blindedpay[0]->fee_base_msat = best->feebase;
ir->inv->blindedpay[0]->fee_proportional_millionths = best->feeppm; ir->inv->invoice_blindedpay[0]->fee_proportional_millionths = best->feeppm;
ir->inv->blindedpay[0]->cltv_expiry_delta = best->cltv; ir->inv->invoice_blindedpay[0]->cltv_expiry_delta = best->cltv;
ir->inv->blindedpay[0]->htlc_minimum_msat = best->htlc_min; ir->inv->invoice_blindedpay[0]->htlc_minimum_msat = best->htlc_min;
ir->inv->blindedpay[0]->htlc_maximum_msat = best->htlc_max; ir->inv->invoice_blindedpay[0]->htlc_maximum_msat = best->htlc_max;
ir->inv->blindedpay[0]->features = NULL; ir->inv->invoice_blindedpay[0]->features = NULL;
} }
done: done:
@@ -432,17 +437,17 @@ static struct command_result *check_period(struct command *cmd,
struct command_result *err; struct command_result *err;
/* If we have a recurrence base, that overrides. */ /* If we have a recurrence base, that overrides. */
if (ir->offer->recurrence_base) if (ir->offer->offer_recurrence_base)
basetime = ir->offer->recurrence_base->basetime; basetime = ir->offer->offer_recurrence_base->basetime;
/* BOLT-offers-recurrence #12: /* BOLT-offers-recurrence #12:
* - if the invoice corresponds to an offer with `recurrence`: * - if the invoice corresponds to an offer with `recurrence`:
* - MUST set `recurrence_basetime` to the start of period #0 as * - MUST set `recurrence_basetime` to the start of period #0 as
* calculated by [Period Calculation](#offer-period-calculation). * 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: /* BOLT-offers-recurrence #12:
* - if the offer had `recurrence_base` and `start_any_period` * - 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` * `recurrence_start` field plus the `recurrence_counter`
* `counter` field. * `counter` field.
*/ */
if (ir->offer->recurrence_base if (ir->offer->offer_recurrence_base
&& ir->offer->recurrence_base->start_any_period) { && ir->offer->offer_recurrence_base->start_any_period) {
err = invreq_must_have(cmd, ir, recurrence_start); err = invreq_must_have(cmd, ir, invreq_recurrence_start);
if (err) if (err)
return err; return err;
period_idx += *ir->invreq->recurrence_start; period_idx += *ir->invreq->invreq_recurrence_start;
/* BOLT-offers-recurrence #12: /* BOLT-offers-recurrence #12:
* - MUST set (or not set) `recurrence_start` exactly as the * - MUST set (or not set) `recurrence_start` exactly as the
* invoice_request did. * invreq did.
*/ */
ir->inv->recurrence_start ir->inv->invreq_recurrence_start
= tal_dup(ir->inv, u32, ir->invreq->recurrence_start); = tal_dup(ir->inv, u32, ir->invreq->invreq_recurrence_start);
} else { } else {
/* BOLT-offers-recurrence #12: /* 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 * - MUST consider the period index for this request to be the
* `recurrence_counter` `counter` field. * `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) if (err)
return 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 * - MUST fail the request if the period index is greater than
* `max_period`. * `max_period`.
*/ */
if (ir->offer->recurrence_limit if (ir->offer->offer_recurrence_limit
&& period_idx > *ir->offer->recurrence_limit) { && period_idx > *ir->offer->offer_recurrence_limit) {
return fail_invreq(cmd, ir, return fail_invreq(cmd, ir,
"period_index %"PRIu64" too great", "period_index %"PRIu64" too great",
period_idx); period_idx);
} }
offer_period_paywindow(ir->offer->recurrence, offer_period_paywindow(ir->offer->offer_recurrence,
ir->offer->recurrence_paywindow, ir->offer->offer_recurrence_paywindow,
ir->offer->recurrence_base, ir->offer->offer_recurrence_base,
basetime, period_idx, basetime, period_idx,
&paywindow_start, &paywindow_end); &paywindow_start, &paywindow_end);
if (*ir->inv->created_at < paywindow_start) { if (*ir->inv->invoice_created_at < paywindow_start) {
return fail_invreq(cmd, ir, return fail_invreq(cmd, ir,
"period_index %"PRIu64 "period_index %"PRIu64
" too early (start %"PRIu64")", " too early (start %"PRIu64")",
period_idx, period_idx,
paywindow_start); paywindow_start);
} }
if (*ir->inv->created_at > paywindow_end) { if (*ir->inv->invoice_created_at > paywindow_end) {
return fail_invreq(cmd, ir, return fail_invreq(cmd, ir,
"period_index %"PRIu64 "period_index %"PRIu64
" too late (ended %"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 * - MUST adjust the *base invoice amount* proportional to time
* remaining in the period. * remaining in the period.
*/ */
if (*ir->invreq->recurrence_counter != 0 if (*ir->invreq->invreq_recurrence_counter != 0
&& ir->offer->recurrence_paywindow && ir->offer->offer_recurrence_paywindow
&& ir->offer->recurrence_paywindow->proportional_amount == 1) { && ir->offer->offer_recurrence_paywindow->proportional_amount == 1) {
u64 start = offer_period_start(basetime, period_idx, u64 start = offer_period_start(basetime, period_idx,
ir->offer->recurrence); ir->offer->offer_recurrence);
u64 end = offer_period_start(basetime, period_idx + 1, u64 end = offer_period_start(basetime, period_idx + 1,
ir->offer->recurrence); ir->offer->offer_recurrence);
if (*ir->inv->created_at > start) { if (*ir->inv->invoice_created_at > start) {
*ir->inv->amount *ir->inv->invoice_amount
*= (double)((*ir->inv->created_at - start) *= (double)((*ir->inv->invoice_created_at - start)
/ (end - start)); / (end - start));
/* Round up to make it non-zero if necessary. */ /* Round up to make it non-zero if necessary. */
if (*ir->inv->amount == 0) if (*ir->inv->invoice_amount == 0)
*ir->inv->amount = 1; *ir->inv->invoice_amount = 1;
} }
} }
@@ -559,7 +564,7 @@ static struct command_result *prev_invoice_done(struct command *cmd,
if (arr->size == 0) { if (arr->size == 0) {
return fail_invreq(cmd, ir, return fail_invreq(cmd, ir,
"No previous invoice #%u", "No previous invoice #%u",
*ir->inv->recurrence_counter - 1); *ir->inv->invreq_recurrence_counter - 1);
} }
/* Was it paid? */ /* Was it paid? */
@@ -567,7 +572,7 @@ static struct command_result *prev_invoice_done(struct command *cmd,
if (!json_tok_streq(buf, status, "paid")) { if (!json_tok_streq(buf, status, "paid")) {
return fail_invreq(cmd, ir, return fail_invreq(cmd, ir,
"Previous invoice #%u status %.*s", "Previous invoice #%u status %.*s",
*ir->inv->recurrence_counter - 1, *ir->inv->invreq_recurrence_counter - 1,
json_tok_full_len(status), json_tok_full_len(status),
json_tok_full(buf, status)); json_tok_full(buf, status));
} }
@@ -577,7 +582,7 @@ static struct command_result *prev_invoice_done(struct command *cmd,
if (!b12) { if (!b12) {
return fail_internalerr(cmd, ir, return fail_internalerr(cmd, ir,
"Previous invoice #%u no bolt12 (%.*s)", "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_len(arr + 1),
json_tok_full(buf, 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_len(b12),
json_tok_full(buf, b12)); json_tok_full(buf, b12));
} }
if (!previnv->recurrence_basetime) { if (!previnv->invoice_recurrence_basetime) {
return fail_internalerr(cmd, ir, return fail_internalerr(cmd, ir,
"Previous invoice %.*s no recurrence_basetime?", "Previous invoice %.*s no recurrence_basetime?",
json_tok_full_len(b12), json_tok_full(buf, b12)); 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 */ /* 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; struct out_req *req;
/* No previous? Just pass through */ /* No previous? Just pass through */
if (*ir->invreq->recurrence_counter == 0) if (*ir->invreq->invreq_recurrence_counter == 0)
return check_period(cmd, ir, *ir->inv->created_at); return check_period(cmd, ir, *ir->inv->invoice_created_at);
req = jsonrpc_request_start(cmd->plugin, cmd, req = jsonrpc_request_start(cmd->plugin, cmd,
"listinvoices", "listinvoices",
@@ -614,9 +619,9 @@ static struct command_result *check_previous_invoice(struct command *cmd,
error, error,
ir); ir);
json_add_label(req->js, json_add_label(req->js,
ir->invreq->offer_id, &ir->offer_id,
ir->invreq->payer_key, ir->invreq->invreq_payer_id,
*ir->invreq->recurrence_counter - 1); *ir->invreq->invreq_recurrence_counter - 1);
return send_outreq(cmd->plugin, req); 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, const struct invreq *ir,
u64 *raw_amt) u64 *raw_amt)
{ {
assert(ir->offer->amount); assert(ir->offer->offer_amount);
/* BOLT-offers #12: /* BOLT-offers #12:
* - MUST calculate the *base invoice amount* using the offer `amount`: * - MUST calculate the *base invoice amount* using the offer `amount`:
*/ */
*raw_amt = *ir->offer->amount; *raw_amt = *ir->offer->offer_amount;
/* BOLT-offers #12: /* BOLT-offers #12:
* - if request contains `quantity`, multiply by `quantity`. * - if request contains `quantity`, multiply by `quantity`.
*/ */
if (ir->invreq->quantity) { if (ir->invreq->invreq_quantity) {
if (mul_overflows_u64(*ir->invreq->quantity, *raw_amt)) { if (mul_overflows_u64(*ir->invreq->invreq_quantity, *raw_amt)) {
return fail_invreq(cmd, ir, return fail_invreq(cmd, ir,
"quantity %"PRIu64 "quantity %"PRIu64
" causes overflow", " causes overflow",
*ir->invreq->quantity); *ir->invreq->invreq_quantity);
} }
*raw_amt *= *ir->invreq->quantity; *raw_amt *= *ir->invreq->invreq_quantity;
} }
return NULL; return NULL;
@@ -669,28 +674,28 @@ static struct command_result *invreq_base_amount_simple(struct command *cmd,
{ {
struct command_result *err; struct command_result *err;
if (ir->offer->amount) { if (ir->offer->offer_amount) {
u64 raw_amount; u64 raw_amount;
assert(!ir->offer->currency); assert(!ir->offer->offer_currency);
err = invreq_amount_by_quantity(cmd, ir, &raw_amount); err = invreq_amount_by_quantity(cmd, ir, &raw_amount);
if (err) if (err)
return err; return err;
*amt = amount_msat(raw_amount); *amt = amount_msat(raw_amount);
} else { } else {
/* BOLT-offers-recurrence #12: /* BOLT-offers #12:
* *
* - otherwise: * The reader:
* - MUST fail the request if it does not contain `amount`. *...
* - MUST use the request `amount` as the *base invoice amount*. * - otherwise (no `offer_amount`):
* (Note: invoice amount can be further modified by recurrence * - MUST fail the request if it does not contain
* below) * `invreq_amount`.
*/ */
err = invreq_must_have(cmd, ir, amount); err = invreq_must_have(cmd, ir, invreq_amount);
if (err) if (err)
return err; return err;
*amt = amount_msat(*ir->invreq->amount); *amt = amount_msat(*ir->invreq->invreq_amount);
} }
return NULL; 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 * - MUST fail the request if its `amount` is less than the
* *base invoice amount*. * *base invoice amount*.
*/ */
if (ir->offer->amount && ir->invreq->amount) { if (ir->offer->offer_amount && ir->invreq->invreq_amount) {
if (amount_msat_less(amount_msat(*ir->invreq->amount), base_inv_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", return fail_invreq(cmd, ir, "Amount must be at least %s",
type_to_string(tmpctx, struct amount_msat, type_to_string(tmpctx, struct amount_msat,
&base_inv_amount)); &base_inv_amount));
@@ -717,7 +722,7 @@ static struct command_result *handle_amount_and_recurrence(struct command *cmd,
* the *base invoice amount*. * the *base invoice amount*.
*/ */
/* Much == 5? Easier to divide and compare, than multiply. */ /* 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)) { base_inv_amount)) {
return fail_invreq(cmd, ir, "Amount vastly exceeds %s", return fail_invreq(cmd, ir, "Amount vastly exceeds %s",
type_to_string(tmpctx, struct amount_msat, 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 * - MUST use the request's `amount` as the *base invoice
* amount*. * 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 */ /* This may be adjusted by recurrence if proportional_amount set */
ir->inv->amount = tal_dup(ir->inv, u64, ir->inv->invoice_amount = tal_dup(ir->inv, u64,
&base_inv_amount.millisatoshis); /* Raw: wire protocol */ &base_inv_amount.millisatoshis); /* Raw: wire protocol */
/* Last of all, we handle recurrence details, which often requires /* Last of all, we handle recurrence details, which often requires
* further lookups. */ * further lookups. */
if (ir->inv->invreq_recurrence_counter) {
/* 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;
return check_previous_invoice(cmd, ir); return check_previous_invoice(cmd, ir);
} }
/* We're happy with 2 hours timeout (default): they can always /* 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) if (!msat)
return fail_internalerr(cmd, ir, return fail_internalerr(cmd, ir,
"Cannot convert currency %.*s: %.*s", "Cannot convert currency %.*s: %.*s",
(int)tal_bytelen(ir->offer->currency), (int)tal_bytelen(ir->offer->offer_currency),
(const char *)ir->offer->currency, (const char *)ir->offer->offer_currency,
json_tok_full_len(result), json_tok_full_len(result),
json_tok_full(buf, result)); json_tok_full(buf, result));
if (!json_to_msat(buf, msat, &amount)) if (!json_to_msat(buf, msat, &amount))
return fail_internalerr(cmd, ir, return fail_internalerr(cmd, ir,
"Bad convert for currency %.*s: %.*s", "Bad convert for currency %.*s: %.*s",
(int)tal_bytelen(ir->offer->currency), (int)tal_bytelen(ir->offer->offer_currency),
(const char *)ir->offer->currency, (const char *)ir->offer->offer_currency,
json_tok_full_len(msat), json_tok_full_len(msat),
json_tok_full(buf, msat)); json_tok_full(buf, msat));
@@ -789,7 +788,7 @@ static struct command_result *convert_currency(struct command *cmd,
struct command_result *err; struct command_result *err;
const struct iso4217_name_and_divisor *iso4217; const struct iso4217_name_and_divisor *iso4217;
assert(ir->offer->currency); assert(ir->offer->offer_currency);
/* Multiply by quantity *first*, for best precision */ /* Multiply by quantity *first*, for best precision */
err = invreq_amount_by_quantity(cmd, ir, &raw_amount); 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 * - if offer `currency` is not the invoice currency, convert
* to the invoice currency. * to the invoice currency.
*/ */
iso4217 = find_iso4217(ir->offer->currency, iso4217 = find_iso4217(ir->offer->offer_currency,
tal_bytelen(ir->offer->currency)); tal_bytelen(ir->offer->offer_currency));
/* We should not create offer with unknown currency! */ /* We should not create offer with unknown currency! */
if (!iso4217) if (!iso4217)
return fail_internalerr(cmd, ir, return fail_internalerr(cmd, ir,
"Unknown offer currency %.*s", "Unknown offer currency %.*s",
(int)tal_bytelen(ir->offer->currency), (int)tal_bytelen(ir->offer->offer_currency),
ir->offer->currency); ir->offer->offer_currency);
double_amount = (double)raw_amount; double_amount = (double)raw_amount;
for (size_t i = 0; i < iso4217->minor_unit; i++) for (size_t i = 0; i < iso4217->minor_unit; i++)
double_amount /= 10; double_amount /= 10;
@@ -817,8 +816,8 @@ static struct command_result *convert_currency(struct command *cmd,
req = jsonrpc_request_start(cmd->plugin, cmd, "currencyconvert", req = jsonrpc_request_start(cmd->plugin, cmd, "currencyconvert",
currency_done, error, ir); currency_done, error, ir);
json_add_stringn(req->js, "currency", json_add_stringn(req->js, "currency",
(const char *)ir->offer->currency, (const char *)ir->offer->offer_currency,
tal_bytelen(ir->offer->currency)); tal_bytelen(ir->offer->offer_currency));
json_add_primitive_fmt(req->js, "amount", "%f", double_amount); json_add_primitive_fmt(req->js, "amount", "%f", double_amount);
return send_outreq(cmd->plugin, req); return send_outreq(cmd->plugin, req);
} }
@@ -837,8 +836,8 @@ static struct command_result *listoffers_done(struct command *cmd,
/* BOLT-offers #12: /* BOLT-offers #12:
* *
* - MUST fail the request if the `offer_id` does not refer to an * - MUST fail the request if the offer fields do not exactly match a
* unexpired offer. * valid, unexpired offer.
*/ */
if (arr->size == 0) if (arr->size == 0)
return fail_invreq(cmd, ir, "Unknown offer"); 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_len(offertok),
json_tok_full(buf, offertok)); json_tok_full(buf, offertok));
} }
/* FIXME-OFFERS: we have these fields in invreq! */
ir->offer = offer_decode(ir, ir->offer = offer_decode(ir,
buf + b12tok->start, buf + b12tok->start,
b12tok->end - b12tok->start, b12tok->end - b12tok->start,
@@ -876,63 +877,65 @@ static struct command_result *listoffers_done(struct command *cmd,
json_tok_full(buf, offertok)); json_tok_full(buf, offertok));
} }
if (ir->offer->absolute_expiry if (ir->offer->offer_absolute_expiry
&& time_now().ts.tv_sec >= *ir->offer->absolute_expiry) { && time_now().ts.tv_sec >= *ir->offer->offer_absolute_expiry) {
/* FIXME: do deloffer to disable it */ /* FIXME: do deloffer to disable it */
return fail_invreq(cmd, ir, "Offer expired"); return fail_invreq(cmd, ir, "Offer expired");
} }
/* BOLT-offers #12: /* BOLT-offers #12:
* - if the offer had a `quantity_min` or `quantity_max` field: * - if `offer_quantity_max` is present:
* - MUST fail the request if there is no `quantity` field. * - MUST fail the request if there is no `invreq_quantity` field.
* - MUST fail the request if there is `quantity` is not within * - if `offer_quantity_max` is non-zero:
* that (inclusive) range. * - MUST fail the request if `invreq_quantity` is zero, OR greater than
* `offer_quantity_max`.
* - otherwise: * - 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) { if (ir->offer->offer_quantity_max) {
err = invreq_must_have(cmd, ir, quantity); err = invreq_must_have(cmd, ir, invreq_quantity);
if (err) if (err)
return err; return err;
if (ir->offer->quantity_min && if (*ir->invreq->invreq_quantity == 0)
*ir->invreq->quantity < *ir->offer->quantity_min) {
return fail_invreq(cmd, ir, return fail_invreq(cmd, ir,
"quantity %"PRIu64 " < %"PRIu64, "quantity zero invalid");
*ir->invreq->quantity,
*ir->offer->quantity_min);
}
if (ir->offer->quantity_max && if (*ir->offer->offer_quantity_max &&
*ir->invreq->quantity > *ir->offer->quantity_max) { *ir->invreq->invreq_quantity > *ir->offer->offer_quantity_max) {
return fail_invreq(cmd, ir, return fail_invreq(cmd, ir,
"quantity %"PRIu64" > %"PRIu64, "quantity %"PRIu64" > %"PRIu64,
*ir->invreq->quantity, *ir->invreq->invreq_quantity,
*ir->offer->quantity_max); *ir->offer->offer_quantity_max);
} }
} else { } else {
err = invreq_must_not_have(cmd, ir, quantity); err = invreq_must_not_have(cmd, ir, invreq_quantity);
if (err) if (err)
return 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); err = invreq_must_have(cmd, ir, signature);
if (err) if (err)
return err; return err;
if (!check_payer_sig(cmd, ir->invreq, if (!check_payer_sig(cmd, ir->invreq,
ir->invreq->payer_key, ir->invreq->invreq_payer_id,
ir->invreq->signature)) { ir->invreq->signature)) {
return fail_invreq(cmd, ir, "bad signature"); return fail_invreq(cmd, ir, "bad signature");
} }
if (ir->offer->recurrence) { if (ir->offer->offer_recurrence) {
/* BOLT-offers-recurrence #12: /* BOLT-offers-recurrence #12:
* *
* - if the offer had a `recurrence`: * - if the offer had a `recurrence`:
* - MUST fail the request if there is no `recurrence_counter` * - MUST fail the request if there is no `recurrence_counter`
* field. * field.
*/ */
err = invreq_must_have(cmd, ir, recurrence_counter); err = invreq_must_have(cmd, ir, invreq_recurrence_counter);
if (err) if (err)
return err; return err;
} else { } else {
@@ -943,78 +946,54 @@ static struct command_result *listoffers_done(struct command *cmd,
* - MUST fail the request if there is a `recurrence_start` * - MUST fail the request if there is a `recurrence_start`
* field. * field.
*/ */
err = invreq_must_not_have(cmd, ir, recurrence_counter); err = invreq_must_not_have(cmd, ir, invreq_recurrence_counter);
if (err) if (err)
return err; return err;
err = invreq_must_not_have(cmd, ir, recurrence_start); err = invreq_must_not_have(cmd, ir, invreq_recurrence_start);
if (err) if (err)
return err; return err;
} }
ir->inv = tlv_invoice_new(cmd);
/* BOLT-offers #12: /* BOLT-offers #12:
* - if the chain for the invoice is not solely bitcoin: * The writer:
* - MUST specify `chains` the offer is valid for. * - MUST copy all non-signature fields from the invreq (including
* unknown fields).
*/ */
if (!streq(chainparams->network_name, "bitcoin")) { ir->inv = invoice_for_invreq(cmd, ir->invreq);
ir->inv->chain = tal_dup(ir->inv, struct bitcoin_blkid, assert(ir->inv->invreq_payer_id);
&chainparams->genesis_blockhash);
}
/* BOLT-offers #12: /* BOLT-offers #12:
* - MUST set `offer_id` to the id of the offer. * - if `offer_node_id` is present:
* - MUST set `invoice_node_id` to `offer_node_id`.
*/ */
/* Which is the same as the invreq */ /* We always provide an offer_node_id! */
ir->inv->offer_id = tal_dup(ir->inv, struct sha256, ir->inv->invoice_node_id = ir->inv->offer_node_id;
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 (ir->offer->quantity_min || ir->offer->quantity_max)
ir->inv->quantity = tal_dup(ir->inv, u64, ir->invreq->quantity);
/* BOLT-offers #12: /* 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->inv->invoice_created_at = tal(ir->inv, u64);
ir->invreq->payer_key); *ir->inv->invoice_created_at = time_now().ts.tv_sec;
/* BOLT-offers #12: /* BOLT-offers #12:
* - MUST set (or not set) `payer_info` exactly as the invoice_request * - MUST set `invoice_payment_hash` to the SHA256 hash of the
* did. * `payment_preimage` that will be given in return for payment.
*/ */
ir->inv->payer_info
= tal_dup_talarr(ir->inv, u8, ir->invreq->payer_info);
/* BOLT-offers #12:
* - MUST set (or not set) `payer_note` exactly as the invoice_request
* did, or MUST not set it.
*/
/* 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)); randombytes_buf(&ir->preimage, sizeof(ir->preimage));
ir->inv->payment_hash = tal(ir->inv, struct sha256); ir->inv->invoice_payment_hash = tal(ir->inv, struct sha256);
sha256(ir->inv->payment_hash, &ir->preimage, sizeof(ir->preimage)); sha256(ir->inv->invoice_payment_hash,
&ir->preimage, sizeof(ir->preimage));
ir->inv->cltv = tal_dup(ir->inv, u16, &cltv_final); /* BOLT-offers #12:
* - or if it allows multiple parts to pay the invoice:
ir->inv->created_at = tal(ir->inv, u64); * - MUST set `invoice_features`.`features` bit `MPP/optional`
*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. */ /* 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); return convert_currency(cmd, ir);
err = invreq_base_amount_simple(cmd, ir, &amt); 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); 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, struct command_result *handle_invoice_request(struct command *cmd,
const u8 *invreqbin, const u8 *invreqbin,
struct blinded_path *reply_path) struct blinded_path *reply_path)
{ {
size_t len = tal_count(invreqbin); size_t len = tal_count(invreqbin);
const u8 *cursor = invreqbin;
struct invreq *ir = tal(cmd, struct invreq); struct invreq *ir = tal(cmd, struct invreq);
struct out_req *req; struct out_req *req;
int bad_feature; int bad_feature;
ir->reply_path = tal_steal(ir, reply_path); 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) { if (!ir->invreq) {
return fail_invreq(cmd, ir, return fail_invreq(cmd, ir,
"Invalid invreq %s", "Invalid invreq %s",
@@ -1050,13 +1023,39 @@ struct command_result *handle_invoice_request(struct command *cmd,
/* BOLT-offers #12: /* 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), bad_feature = features_unsupported(plugin_feature_set(cmd->plugin),
ir->invreq->features, ir->invreq->invreq_features,
BOLT11_FEATURE); BOLT12_INVREQ_FEATURE);
if (bad_feature != -1) { if (bad_feature != -1) {
return fail_invreq(cmd, ir, return fail_invreq(cmd, ir,
"Unsupported invreq feature %i", "Unsupported invreq feature %i",
@@ -1065,34 +1064,41 @@ struct command_result *handle_invoice_request(struct command *cmd,
/* BOLT-offers #12: /* BOLT-offers #12:
* *
* The reader of an invoice_request: * The reader:
*... *...
* - if `chain` is not present: * - if `invreq_chain` is not present:
* - MUST fail the request if bitcoin is not a supported chain. * - MUST fail the request if bitcoin is not a supported chain.
* - otherwise: * - otherwise:
* - MUST fail the request if `chain` is not a supported chain. * - 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, return fail_invreq(cmd, ir,
"Wrong chain %s", "Wrong chain %s",
tal_hex(tmpctx, ir->invreq->chain)); tal_hex(tmpctx, ir->invreq->invreq_chain));
} }
/* BOLT-offers #12: /* BOLT-offers #12:
* *
* The reader of an invoice_request: * - otherwise (no `offer_node_id`, not a response to our offer):
* - MUST fail the request if `payer_key` is not present.
*/ */
if (!ir->invreq->payer_key) /* FIXME-OFFERS: handle this! */
return fail_invreq(cmd, ir, "Missing payer key"); if (!ir->invreq->offer_node_id) {
return fail_invreq(cmd, ir, "Not based on an offer");
}
if (!ir->invreq->offer_id) /* BOLT-offers #12:
return handle_offerless_request(cmd, ir); *
* - 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 */ /* Now, look up offer */
req = jsonrpc_request_start(cmd->plugin, cmd, "listoffers", req = jsonrpc_request_start(cmd->plugin, cmd, "listoffers",
listoffers_done, error, ir); 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); return send_outreq(cmd->plugin, req);
} }

View File

@@ -21,7 +21,7 @@ static bool msat_or_any(const char *buffer,
buffer + tok->start, tok->end - tok->start)) buffer + tok->start, tok->end - tok->start))
return false; return false;
offer->amount = tal_dup(offer, u64, offer->offer_amount = tal_dup(offer, u64,
&msat.millisatoshis); /* Raw: other currencies */ &msat.millisatoshis); /* Raw: other currencies */
return true; return true;
} }
@@ -39,7 +39,7 @@ static struct command_result *param_amount(struct command *cmd,
if (msat_or_any(buffer, tok, offer)) if (msat_or_any(buffer, tok, offer))
return NULL; return NULL;
offer->amount = tal(offer, u64); offer->offer_amount = tal(offer, u64);
/* BOLT-offers #12: /* BOLT-offers #12:
* *
@@ -58,7 +58,7 @@ static struct command_result *param_amount(struct command *cmd,
ISO4217_NAMELEN, ISO4217_NAMELEN,
buffer + tok->end - ISO4217_NAMELEN); buffer + tok->end - ISO4217_NAMELEN);
offer->currency offer->offer_currency
= tal_dup_arr(offer, utf8, isocode->name, ISO4217_NAMELEN, 0); = tal_dup_arr(offer, utf8, isocode->name, ISO4217_NAMELEN, 0);
number = *tok; number = *tok;
@@ -77,19 +77,19 @@ static struct command_result *param_amount(struct command *cmd,
"Bad minor units"); "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, return command_fail_badparam(cmd, name, buffer, tok,
"should be 'any', msatoshis or <ISO-4712><amount>[.<amount>]"); "should be 'any', msatoshis or <ISO-4712><amount>[.<amount>]");
for (size_t i = 0; i < isocode->minor_unit; i++) { 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, return command_fail_badparam(cmd, name, buffer,
&whole, &whole,
"excessively large value"); "excessively large value");
*offer->amount *= 10; *offer->offer_amount *= 10;
} }
*offer->amount += cents; *offer->offer_amount += cents;
return NULL; return NULL;
} }
@@ -139,8 +139,7 @@ static struct command_result *param_recurrence(struct command *cmd,
const char *name, const char *name,
const char *buffer, const char *buffer,
const jsmntok_t *tok, const jsmntok_t *tok,
struct tlv_offer_recurrence struct recurrence **recurrence)
**recurrence)
{ {
u32 mul; u32 mul;
const struct time_string *ts; 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, return command_fail_badparam(cmd, name, buffer, tok,
"not a valid time"); "not a valid time");
*recurrence = tal(cmd, struct tlv_offer_recurrence); *recurrence = tal(cmd, struct recurrence);
(*recurrence)->time_unit = ts->unit; (*recurrence)->time_unit = ts->unit;
(*recurrence)->period = ts->mul * mul; (*recurrence)->period = ts->mul * mul;
return NULL; return NULL;
@@ -160,12 +159,12 @@ static struct command_result *param_recurrence_base(struct command *cmd,
const char *name, const char *name,
const char *buffer, const char *buffer,
const jsmntok_t *tok, const jsmntok_t *tok,
struct tlv_offer_recurrence_base **base) struct recurrence_base **base)
{ {
/* Make copy so we can manipulate it */ /* Make copy so we can manipulate it */
jsmntok_t t = *tok; jsmntok_t t = *tok;
*base = tal(cmd, struct tlv_offer_recurrence_base); *base = tal(cmd, struct recurrence_base);
if (json_tok_startswith(buffer, &t, "@")) { if (json_tok_startswith(buffer, &t, "@")) {
t.start++; t.start++;
(*base)->start_any_period = false; (*base)->start_any_period = false;
@@ -183,12 +182,12 @@ static struct command_result *param_recurrence_paywindow(struct command *cmd,
const char *name, const char *name,
const char *buffer, const char *buffer,
const jsmntok_t *tok, const jsmntok_t *tok,
struct tlv_offer_recurrence_paywindow struct recurrence_paywindow
**paywindow) **paywindow)
{ {
jsmntok_t t, before, after; jsmntok_t t, before, after;
*paywindow = tal(cmd, struct tlv_offer_recurrence_paywindow); *paywindow = tal(cmd, struct recurrence_paywindow);
t = *tok; t = *tok;
if (json_tok_endswith(buffer, &t, "%")) { if (json_tok_endswith(buffer, &t, "%")) {
(*paywindow)->proportional_amount = true; (*paywindow)->proportional_amount = true;
@@ -231,7 +230,7 @@ static struct command_result *check_result(struct command *cmd,
&active)) { &active)) {
return command_fail(cmd, return command_fail(cmd,
LIGHTNINGD, LIGHTNINGD,
"Bad creaoffer status reply %.*s", "Bad createoffer status reply %.*s",
json_tok_full_len(result), json_tok_full_len(result),
json_tok_full(buf, 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_req("description", param_escaped_string, &desc),
p_opt("issuer", param_escaped_string, &issuer), p_opt("issuer", param_escaped_string, &issuer),
p_opt("label", param_escaped_string, &offinfo->label), p_opt("label", param_escaped_string, &offinfo->label),
p_opt("quantity_min", param_u64, &offer->quantity_min), p_opt("quantity_max", param_u64, &offer->offer_quantity_max),
p_opt("quantity_max", param_u64, &offer->quantity_max), p_opt("absolute_expiry", param_u64, &offer->offer_absolute_expiry),
p_opt("absolute_expiry", param_u64, &offer->absolute_expiry), p_opt("recurrence", param_recurrence, &offer->offer_recurrence),
p_opt("recurrence", param_recurrence, &offer->recurrence),
p_opt("recurrence_base", p_opt("recurrence_base",
param_recurrence_base, param_recurrence_base,
&offer->recurrence_base), &offer->offer_recurrence_base),
p_opt("recurrence_paywindow", p_opt("recurrence_paywindow",
param_recurrence_paywindow, param_recurrence_paywindow,
&offer->recurrence_paywindow), &offer->offer_recurrence_paywindow),
p_opt("recurrence_limit", p_opt("recurrence_limit",
param_number, param_number,
&offer->recurrence_limit), &offer->offer_recurrence_limit),
p_opt_def("single_use", param_bool, p_opt_def("single_use", param_bool,
&offinfo->single_use, false), &offinfo->single_use, false),
/* FIXME: hints support! */ /* FIXME: hints support! */
@@ -312,66 +310,66 @@ struct command_result *json_offer(struct command *cmd,
return command_fail(cmd, LIGHTNINGD, return command_fail(cmd, LIGHTNINGD,
"experimental-offers not enabled"); "experimental-offers not enabled");
/* BOLT-offers #12: /* Doesn't make sense to have max quantity 1. */
* - MUST NOT set `quantity_min` or `quantity_max` less than 1. if (offer->offer_quantity_max && *offer->offer_quantity_max == 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)
return command_fail_badparam(cmd, "quantity_max", return command_fail_badparam(cmd, "quantity_max",
buffer, params, buffer, params,
"must be >= 1"); "must be 0 or > 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");
}
/* BOLT-offers #12: /* BOLT-offers #12:
* *
* - if the chain for the invoice is not solely bitcoin: * - 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: * - 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")) { if (!streq(chainparams->network_name, "bitcoin")) {
offer->chains = tal_arr(offer, struct bitcoin_blkid, 1); offer->offer_chains = tal_arr(offer, struct bitcoin_blkid, 1);
offer->chains[0] = chainparams->genesis_blockhash; offer->offer_chains[0] = chainparams->genesis_blockhash;
} }
if (!offer->recurrence) { if (!offer->offer_recurrence) {
if (offer->recurrence_limit) if (offer->offer_recurrence_limit)
return command_fail_badparam(cmd, "recurrence_limit", return command_fail_badparam(cmd, "recurrence_limit",
buffer, params, buffer, params,
"needs recurrence"); "needs recurrence");
if (offer->recurrence_base) if (offer->offer_recurrence_base)
return command_fail_badparam(cmd, "recurrence_base", return command_fail_badparam(cmd, "recurrence_base",
buffer, params, buffer, params,
"needs recurrence"); "needs recurrence");
if (offer->recurrence_paywindow) if (offer->offer_recurrence_paywindow)
return command_fail_badparam(cmd, "recurrence_paywindow", return command_fail_badparam(cmd, "recurrence_paywindow",
buffer, params, buffer, params,
"needs recurrence"); "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) { if (issuer) {
offer->issuer offer->offer_issuer
= tal_dup_arr(offer, char, issuer, strlen(issuer), 0); = 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 /* If they specify a different currency, warn if we can't
* convert it! */ * convert it! */
if (offer->currency) { if (offer->offer_currency) {
struct out_req *req; struct out_req *req;
req = jsonrpc_request_start(cmd->plugin, cmd, "currencyconvert", req = jsonrpc_request_start(cmd->plugin, cmd, "currencyconvert",
@@ -379,8 +377,8 @@ struct command_result *json_offer(struct command *cmd,
offinfo); offinfo);
json_add_u32(req->js, "amount", 1); json_add_u32(req->js, "amount", 1);
json_add_stringn(req->js, "currency", json_add_stringn(req->js, "currency",
(const char *)offer->currency, (const char *)offer->offer_currency,
tal_bytelen(offer->currency)); tal_bytelen(offer->offer_currency));
return send_outreq(cmd->plugin, req); return send_outreq(cmd->plugin, req);
} }

View File

@@ -1092,83 +1092,73 @@ static struct command_result *json_pay(struct command *cmd,
/* p->features = tal_steal(p, b12->features); */ /* p->features = tal_steal(p, b12->features); */
p->features = NULL; p->features = NULL;
if (!b12->node_id) if (!b12->invoice_node_id)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"invoice missing node_id"); "invoice missing node_id");
if (!b12->payment_hash) if (!b12->invoice_payment_hash)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"invoice missing payment_hash"); "invoice missing payment_hash");
if (!b12->created_at) if (!b12->invoice_created_at)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"invoice missing created_at"); "invoice missing created_at");
if (b12->amount) { if (!b12->invoice_amount)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"invoice missing invoice_amount");
invmsat = tal(cmd, struct amount_msat); invmsat = tal(cmd, struct amount_msat);
*invmsat = amount_msat(*b12->amount); *invmsat = amount_msat(*b12->invoice_amount);
} else
invmsat = NULL;
p->destination = tal(p, struct node_id); p->destination = tal(p, struct node_id);
node_id_from_pubkey(p->destination, b12->node_id); node_id_from_pubkey(p->destination, b12->invoice_node_id);
p->payment_hash = tal_dup(p, struct sha256, b12->payment_hash); p->payment_hash = tal_dup(p, struct sha256,
if (b12->recurrence_counter && !label) b12->invoice_payment_hash);
if (b12->invreq_recurrence_counter && !label)
return command_fail( return command_fail(
cmd, JSONRPC2_INVALID_PARAMS, cmd, JSONRPC2_INVALID_PARAMS,
"recurring invoice requires a label"); "recurring invoice requires a label");
/* BOLT-offers #12: /* 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->invoice_paths) == 0)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"invoice missing invoice_paths");
if (tal_count(b12->paths) != 0) { /* BOLT-offers #12:
/* BOLT-offers #12: - MUST reject the invoice if * - MUST reject the invoice if `invoice_blindedpay` does not
* `blindedpay` does not contain exactly one * contain exactly one `blinded_payinfo` per
* `blinded_payinfo` per `blinded_path`. * `invoice_paths`.`blinded_path`. */
*/ if (tal_count(b12->invoice_paths)
if (tal_count(b12->paths) != tal_count(b12->blindedpay)) { != tal_count(b12->invoice_blindedpay)) {
return command_fail( return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
cmd, JSONRPC2_INVALID_PARAMS,
"Wrong blinding info: %zu paths, %zu payinfo", "Wrong blinding info: %zu paths, %zu payinfo",
tal_count(b12->paths), tal_count(b12->invoice_paths),
tal_count(b12->blindedpay)); tal_count(b12->invoice_blindedpay));
} }
/* FIXME: do MPP across these! We choose first one. */ /* FIXME: do MPP across these! We choose first one. */
p->blindedpath = tal_steal(p, b12->paths[0]); p->blindedpath = tal_steal(p, b12->invoice_paths[0]);
p->blindedpay = tal_steal(p, b12->blindedpay[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 */ /* Set destination to introduction point */
node_id_from_pubkey(p->destination, &p->blindedpath->first_node_id); 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));
}
p->payment_metadata = NULL; p->payment_metadata = NULL;
p->routes = NULL; p->routes = NULL;
if (b12->cltv)
p->min_final_cltv_expiry = *b12->cltv;
else
p->min_final_cltv_expiry = 18;
/* BOLT-offers #12: /* BOLT-offers #12:
* - if `relative_expiry` is present: * - if `invoice_relative_expiry` is present:
* - MUST reject the invoice if the current time since * - 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`. * `seconds_from_creation`.
* - otherwise: * - otherwise:
* - MUST reject the invoice if the current time since * - 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
* 7200. * 7200.
*/ */
if (b12->relative_expiry) if (b12->invoice_relative_expiry)
invexpiry = *b12->created_at + *b12->relative_expiry; invexpiry = *b12->invoice_created_at + *b12->invoice_relative_expiry;
else 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); p->local_offer_id = tal_steal(p, local_offer_id);
} }

View File

@@ -4383,7 +4383,7 @@ def test_offer_needs_option(node_factory):
l1.rpc.call('fetchinvoice', {'offer': 'aaaa'}) l1.rpc.call('fetchinvoice', {'offer': 'aaaa'})
# Decode still works though # Decode still works though
assert l1.rpc.decode('lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcgqyys5qq7yypxdeze35wncs2l2u4gfzyrpds00e6yakfrt6ctrw5n9qanzhqr2x8sgp3lqxpxd82j87j67wyff9cd9msgagq8hveftdkx5t3e98gj2x7ac99hhwlpj9yvj79yz3l8gdlmdmhq47ct9pkedfd8naksd8f8gpar')['valid'] assert l1.rpc.decode('lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcgqyqs5pr5v4ehg93pqfnwgkvdr57yzh6h92zg3qctvrm7w38djg67kzcm4yeg8vc4cq63s')['valid']
def test_offer(node_factory, bitcoind): def test_offer(node_factory, bitcoind):
@@ -4449,14 +4449,14 @@ def test_offer(node_factory, bitcoind):
offer['bolt12']]).decode('UTF-8') offer['bolt12']]).decode('UTF-8')
assert 'issuer: ' + weird_issuer in output assert 'issuer: ' + weird_issuer in output
# Test quantity min/max # Test quantity
ret = l1.rpc.call('offer', {'amount': '100000sat', ret = l1.rpc.call('offer', {'amount': '100000sat',
'description': 'quantity_min test', 'description': 'quantity_max existence test',
'quantity_min': 1}) 'quantity_max': 0})
offer = only_one(l1.rpc.call('listoffers', [ret['offer_id']])['offers']) offer = only_one(l1.rpc.call('listoffers', [ret['offer_id']])['offers'])
output = subprocess.check_output([bolt12tool, 'decode', output = subprocess.check_output([bolt12tool, 'decode',
offer['bolt12']]).decode('UTF-8') offer['bolt12']]).decode('UTF-8')
assert 'quantity_min: 1' in output assert 'quantity_max: 0' in output
ret = l1.rpc.call('offer', {'amount': '100000sat', ret = l1.rpc.call('offer', {'amount': '100000sat',
'description': 'quantity_max test', 'description': 'quantity_max test',
@@ -4466,26 +4466,6 @@ def test_offer(node_factory, bitcoind):
offer['bolt12']]).decode('UTF-8') offer['bolt12']]).decode('UTF-8')
assert 'quantity_max: 2' in output 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 # Test absolute_expiry
exp = int(time.time() + 2) exp = int(time.time() + 2)
ret = l1.rpc.call('offer', {'amount': '100000sat', ret = l1.rpc.call('offer', {'amount': '100000sat',
@@ -5241,5 +5221,5 @@ def test_payerkey(node_factory):
"03a3bbda0137722ba62207b9d3e5e6cc2a11e58480f801892093e01383aacb7fb2"] "03a3bbda0137722ba62207b9d3e5e6cc2a11e58480f801892093e01383aacb7fb2"]
for n, k in zip(nodes, expected_keys): 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 assert n.rpc.decode(b12)['payer_key'] == k

View File

@@ -1,119 +1,163 @@
tlvtype,offer,chains,2 tlvtype,offer,offer_chains,2
tlvdata,offer,chains,chains,chain_hash,... tlvdata,offer,offer_chains,chains,chain_hash,...
tlvtype,offer,currency,6 tlvtype,offer,offer_metadata,4
tlvdata,offer,currency,iso4217,utf8,... tlvdata,offer,offer_metadata,data,byte,...
tlvtype,offer,amount,8 tlvtype,offer,offer_currency,6
tlvdata,offer,amount,amount,tu64, tlvdata,offer,offer_currency,iso4217,utf8,...
tlvtype,offer,description,10 tlvtype,offer,offer_amount,8
tlvdata,offer,description,description,utf8,... tlvdata,offer,offer_amount,amount,tu64,
tlvtype,offer,features,12 tlvtype,offer,offer_description,10
tlvdata,offer,features,features,byte,... tlvdata,offer,offer_description,description,utf8,...
tlvtype,offer,absolute_expiry,14 tlvtype,offer,offer_features,12
tlvdata,offer,absolute_expiry,seconds_from_epoch,tu64, tlvdata,offer,offer_features,features,byte,...
tlvtype,offer,paths,16 tlvtype,offer,offer_absolute_expiry,14
tlvdata,offer,paths,paths,blinded_path,... tlvdata,offer,offer_absolute_expiry,seconds_from_epoch,tu64,
tlvtype,offer,issuer,20 tlvtype,offer,offer_paths,16
tlvdata,offer,issuer,issuer,utf8,... tlvdata,offer,offer_paths,paths,blinded_path,...
tlvtype,offer,quantity_min,22 tlvtype,offer,offer_issuer,18
tlvdata,offer,quantity_min,min,tu64, tlvdata,offer,offer_issuer,issuer,utf8,...
tlvtype,offer,quantity_max,24 tlvtype,offer,offer_quantity_max,20
tlvdata,offer,quantity_max,max,tu64, tlvdata,offer,offer_quantity_max,max,tu64,
tlvtype,offer,recurrence,26 tlvtype,offer,offer_node_id,22
tlvdata,offer,recurrence,time_unit,byte, tlvdata,offer,offer_node_id,node_id,point,
tlvdata,offer,recurrence,period,tu32, tlvtype,offer,offer_recurrence,26
tlvtype,offer,recurrence_paywindow,64 tlvdata,offer,offer_recurrence,recurrence,recurrence,
tlvdata,offer,recurrence_paywindow,seconds_before,u32, tlvtype,offer,offer_recurrence_paywindow,28
tlvdata,offer,recurrence_paywindow,proportional_amount,byte, tlvdata,offer,offer_recurrence_paywindow,paywindow,recurrence_paywindow,
tlvdata,offer,recurrence_paywindow,seconds_after,tu32, tlvtype,offer,offer_recurrence_limit,30
tlvtype,offer,recurrence_limit,66 tlvdata,offer,offer_recurrence_limit,max_period,tu32,
tlvdata,offer,recurrence_limit,max_period,tu32, tlvtype,offer,offer_recurrence_base,32
tlvtype,offer,recurrence_base,28 tlvdata,offer,offer_recurrence_base,base,recurrence_base,
tlvdata,offer,recurrence_base,start_any_period,byte, tlvtype,invoice_request,invreq_metadata,0
tlvdata,offer,recurrence_base,basetime,tu64, tlvdata,invoice_request,invreq_metadata,blob,byte,...
tlvtype,offer,node_id,30 tlvtype,invoice_request,offer_chains,2
tlvdata,offer,node_id,node_id,point, tlvdata,invoice_request,offer_chains,chains,chain_hash,...
tlvtype,offer,send_invoice,54 tlvtype,invoice_request,offer_metadata,4
tlvtype,offer,refund_for,34 tlvdata,invoice_request,offer_metadata,data,byte,...
tlvdata,offer,refund_for,refunded_payment_hash,sha256, tlvtype,invoice_request,offer_currency,6
tlvtype,offer,signature,240 tlvdata,invoice_request,offer_currency,iso4217,utf8,...
tlvdata,offer,signature,sig,bip340sig, tlvtype,invoice_request,offer_amount,8
tlvtype,invoice_request,chain,3 tlvdata,invoice_request,offer_amount,amount,tu64,
tlvdata,invoice_request,chain,chain,chain_hash, tlvtype,invoice_request,offer_description,10
tlvtype,invoice_request,offer_id,4 tlvdata,invoice_request,offer_description,description,utf8,...
tlvdata,invoice_request,offer_id,offer_id,sha256, tlvtype,invoice_request,offer_features,12
tlvtype,invoice_request,amount,8 tlvdata,invoice_request,offer_features,features,byte,...
tlvdata,invoice_request,amount,msat,tu64, tlvtype,invoice_request,offer_absolute_expiry,14
tlvtype,invoice_request,features,12 tlvdata,invoice_request,offer_absolute_expiry,seconds_from_epoch,tu64,
tlvdata,invoice_request,features,features,byte,... tlvtype,invoice_request,offer_paths,16
tlvtype,invoice_request,quantity,32 tlvdata,invoice_request,offer_paths,paths,blinded_path,...
tlvdata,invoice_request,quantity,quantity,tu64, tlvtype,invoice_request,offer_issuer,18
tlvtype,invoice_request,recurrence_counter,36 tlvdata,invoice_request,offer_issuer,issuer,utf8,...
tlvdata,invoice_request,recurrence_counter,counter,tu32, tlvtype,invoice_request,offer_quantity_max,20
tlvtype,invoice_request,recurrence_start,68 tlvdata,invoice_request,offer_quantity_max,max,tu64,
tlvdata,invoice_request,recurrence_start,period_offset,tu32, tlvtype,invoice_request,offer_node_id,22
tlvtype,invoice_request,payer_key,38 tlvdata,invoice_request,offer_node_id,node_id,point,
tlvdata,invoice_request,payer_key,key,point, tlvtype,invoice_request,offer_recurrence,26
tlvtype,invoice_request,payer_note,39 tlvdata,invoice_request,offer_recurrence,recurrence,recurrence,
tlvdata,invoice_request,payer_note,note,utf8,... tlvtype,invoice_request,offer_recurrence_paywindow,28
tlvtype,invoice_request,payer_info,50 tlvdata,invoice_request,offer_recurrence_paywindow,paywindow,recurrence_paywindow,
tlvdata,invoice_request,payer_info,blob,byte,... tlvtype,invoice_request,offer_recurrence_limit,30
tlvtype,invoice_request,replace_invoice,56 tlvdata,invoice_request,offer_recurrence_limit,max_period,tu32,
tlvdata,invoice_request,replace_invoice,payment_hash,sha256, 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 tlvtype,invoice_request,signature,240
tlvdata,invoice_request,signature,sig,bip340sig, tlvdata,invoice_request,signature,sig,bip340sig,
tlvtype,invoice,chain,3 tlvtype,invoice,invreq_metadata,0
tlvdata,invoice,chain,chain,chain_hash, tlvdata,invoice,invreq_metadata,blob,byte,...
tlvtype,invoice,offer_id,4 tlvtype,invoice,offer_chains,2
tlvdata,invoice,offer_id,offer_id,sha256, tlvdata,invoice,offer_chains,chains,chain_hash,...
tlvtype,invoice,amount,8 tlvtype,invoice,offer_metadata,4
tlvdata,invoice,amount,msat,tu64, tlvdata,invoice,offer_metadata,data,byte,...
tlvtype,invoice,description,10 tlvtype,invoice,offer_currency,6
tlvdata,invoice,description,description,utf8,... tlvdata,invoice,offer_currency,iso4217,utf8,...
tlvtype,invoice,features,12 tlvtype,invoice,offer_amount,8
tlvdata,invoice,features,features,byte,... tlvdata,invoice,offer_amount,amount,tu64,
tlvtype,invoice,paths,16 tlvtype,invoice,offer_description,10
tlvdata,invoice,paths,paths,blinded_path,... tlvdata,invoice,offer_description,description,utf8,...
tlvtype,invoice,blindedpay,18 tlvtype,invoice,offer_features,12
tlvdata,invoice,blindedpay,payinfo,blinded_payinfo,... tlvdata,invoice,offer_features,features,byte,...
tlvtype,invoice,blinded_capacities,19 tlvtype,invoice,offer_absolute_expiry,14
tlvdata,invoice,blinded_capacities,incoming_msat,u64,... tlvdata,invoice,offer_absolute_expiry,seconds_from_epoch,tu64,
tlvtype,invoice,issuer,20 tlvtype,invoice,offer_paths,16
tlvdata,invoice,issuer,issuer,utf8,... tlvdata,invoice,offer_paths,paths,blinded_path,...
tlvtype,invoice,node_id,30 tlvtype,invoice,offer_issuer,18
tlvdata,invoice,node_id,node_id,point, tlvdata,invoice,offer_issuer,issuer,utf8,...
tlvtype,invoice,quantity,32 tlvtype,invoice,offer_quantity_max,20
tlvdata,invoice,quantity,quantity,tu64, tlvdata,invoice,offer_quantity_max,max,tu64,
tlvtype,invoice,refund_for,34 tlvtype,invoice,offer_node_id,22
tlvdata,invoice,refund_for,refunded_payment_hash,sha256, tlvdata,invoice,offer_node_id,node_id,point,
tlvtype,invoice,recurrence_counter,36 tlvtype,invoice,offer_recurrence,26
tlvdata,invoice,recurrence_counter,counter,tu32, tlvdata,invoice,offer_recurrence,recurrence,recurrence,
tlvtype,invoice,recurrence_start,68 tlvtype,invoice,offer_recurrence_paywindow,28
tlvdata,invoice,recurrence_start,period_offset,tu32, tlvdata,invoice,offer_recurrence_paywindow,paywindow,recurrence_paywindow,
tlvtype,invoice,recurrence_basetime,64 tlvtype,invoice,offer_recurrence_limit,30
tlvdata,invoice,recurrence_basetime,basetime,tu64, tlvdata,invoice,offer_recurrence_limit,max_period,tu32,
tlvtype,invoice,payer_key,38 tlvtype,invoice,offer_recurrence_base,32
tlvdata,invoice,payer_key,key,point, tlvdata,invoice,offer_recurrence_base,base,recurrence_base,
tlvtype,invoice,payer_note,39 tlvtype,invoice,invreq_chain,80
tlvdata,invoice,payer_note,note,utf8,... tlvdata,invoice,invreq_chain,chain,chain_hash,
tlvtype,invoice,created_at,40 tlvtype,invoice,invreq_amount,82
tlvdata,invoice,created_at,timestamp,tu64, tlvdata,invoice,invreq_amount,msat,tu64,
tlvtype,invoice,payment_hash,42 tlvtype,invoice,invreq_features,84
tlvdata,invoice,payment_hash,payment_hash,sha256, tlvdata,invoice,invreq_features,features,byte,...
tlvtype,invoice,relative_expiry,44 tlvtype,invoice,invreq_quantity,86
tlvdata,invoice,relative_expiry,seconds_from_creation,tu32, tlvdata,invoice,invreq_quantity,quantity,tu64,
tlvtype,invoice,cltv,46 tlvtype,invoice,invreq_payer_id,88
tlvdata,invoice,cltv,min_final_cltv_expiry,tu16, tlvdata,invoice,invreq_payer_id,key,point,
tlvtype,invoice,fallbacks,48 tlvtype,invoice,invreq_payer_note,89
tlvdata,invoice,fallbacks,fallbacks,fallback_address,... tlvdata,invoice,invreq_payer_note,note,utf8,...
tlvtype,invoice,payer_info,50 tlvtype,invoice,invreq_recurrence_counter,90
tlvdata,invoice,payer_info,blob,byte,... tlvdata,invoice,invreq_recurrence_counter,counter,tu32,
tlvtype,invoice,refund_signature,52 tlvtype,invoice,invreq_recurrence_start,92
tlvdata,invoice,refund_signature,payer_signature,bip340sig, tlvdata,invoice,invreq_recurrence_start,period_offset,tu32,
tlvtype,invoice,send_invoice,54 tlvtype,invoice,invoice_paths,160
tlvtype,invoice,replace_invoice,56 tlvdata,invoice,invoice_paths,paths,blinded_path,...
tlvdata,invoice,replace_invoice,payment_hash,sha256, 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 tlvtype,invoice,signature,240
tlvdata,invoice,signature,sig,bip340sig, 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 subtype,blinded_payinfo
subtypedata,blinded_payinfo,fee_base_msat,u32, subtypedata,blinded_payinfo,fee_base_msat,u32,
subtypedata,blinded_payinfo,fee_proportional_millionths,u32, subtypedata,blinded_payinfo,fee_proportional_millionths,u32,
1 tlvtype,offer,chains,2 tlvtype,offer,offer_chains,2
2 tlvdata,offer,chains,chains,chain_hash,... tlvdata,offer,offer_chains,chains,chain_hash,...
3 tlvtype,offer,currency,6 tlvtype,offer,offer_metadata,4
4 tlvdata,offer,currency,iso4217,utf8,... tlvdata,offer,offer_metadata,data,byte,...
5 tlvtype,offer,amount,8 tlvtype,offer,offer_currency,6
6 tlvdata,offer,amount,amount,tu64, tlvdata,offer,offer_currency,iso4217,utf8,...
7 tlvtype,offer,description,10 tlvtype,offer,offer_amount,8
8 tlvdata,offer,description,description,utf8,... tlvdata,offer,offer_amount,amount,tu64,
9 tlvtype,offer,features,12 tlvtype,offer,offer_description,10
10 tlvdata,offer,features,features,byte,... tlvdata,offer,offer_description,description,utf8,...
11 tlvtype,offer,absolute_expiry,14 tlvtype,offer,offer_features,12
12 tlvdata,offer,absolute_expiry,seconds_from_epoch,tu64, tlvdata,offer,offer_features,features,byte,...
13 tlvtype,offer,paths,16 tlvtype,offer,offer_absolute_expiry,14
14 tlvdata,offer,paths,paths,blinded_path,... tlvdata,offer,offer_absolute_expiry,seconds_from_epoch,tu64,
15 tlvtype,offer,issuer,20 tlvtype,offer,offer_paths,16
16 tlvdata,offer,issuer,issuer,utf8,... tlvdata,offer,offer_paths,paths,blinded_path,...
17 tlvtype,offer,quantity_min,22 tlvtype,offer,offer_issuer,18
18 tlvdata,offer,quantity_min,min,tu64, tlvdata,offer,offer_issuer,issuer,utf8,...
19 tlvtype,offer,quantity_max,24 tlvtype,offer,offer_quantity_max,20
20 tlvdata,offer,quantity_max,max,tu64, tlvdata,offer,offer_quantity_max,max,tu64,
21 tlvtype,offer,recurrence,26 tlvtype,offer,offer_node_id,22
22 tlvdata,offer,recurrence,time_unit,byte, tlvdata,offer,offer_node_id,node_id,point,
23 tlvdata,offer,recurrence,period,tu32, tlvtype,offer,offer_recurrence,26
24 tlvtype,offer,recurrence_paywindow,64 tlvdata,offer,offer_recurrence,recurrence,recurrence,
25 tlvdata,offer,recurrence_paywindow,seconds_before,u32, tlvtype,offer,offer_recurrence_paywindow,28
26 tlvdata,offer,recurrence_paywindow,proportional_amount,byte, tlvdata,offer,offer_recurrence_paywindow,paywindow,recurrence_paywindow,
27 tlvdata,offer,recurrence_paywindow,seconds_after,tu32, tlvtype,offer,offer_recurrence_limit,30
28 tlvtype,offer,recurrence_limit,66 tlvdata,offer,offer_recurrence_limit,max_period,tu32,
29 tlvdata,offer,recurrence_limit,max_period,tu32, tlvtype,offer,offer_recurrence_base,32
30 tlvtype,offer,recurrence_base,28 tlvdata,offer,offer_recurrence_base,base,recurrence_base,
31 tlvdata,offer,recurrence_base,start_any_period,byte, tlvtype,invoice_request,invreq_metadata,0
32 tlvdata,offer,recurrence_base,basetime,tu64, tlvdata,invoice_request,invreq_metadata,blob,byte,...
33 tlvtype,offer,node_id,30 tlvtype,invoice_request,offer_chains,2
34 tlvdata,offer,node_id,node_id,point, tlvdata,invoice_request,offer_chains,chains,chain_hash,...
35 tlvtype,offer,send_invoice,54 tlvtype,invoice_request,offer_metadata,4
36 tlvtype,offer,refund_for,34 tlvdata,invoice_request,offer_metadata,data,byte,...
37 tlvdata,offer,refund_for,refunded_payment_hash,sha256, tlvtype,invoice_request,offer_currency,6
38 tlvtype,offer,signature,240 tlvdata,invoice_request,offer_currency,iso4217,utf8,...
39 tlvdata,offer,signature,sig,bip340sig, tlvtype,invoice_request,offer_amount,8
40 tlvtype,invoice_request,chain,3 tlvdata,invoice_request,offer_amount,amount,tu64,
41 tlvdata,invoice_request,chain,chain,chain_hash, tlvtype,invoice_request,offer_description,10
42 tlvtype,invoice_request,offer_id,4 tlvdata,invoice_request,offer_description,description,utf8,...
43 tlvdata,invoice_request,offer_id,offer_id,sha256, tlvtype,invoice_request,offer_features,12
44 tlvtype,invoice_request,amount,8 tlvdata,invoice_request,offer_features,features,byte,...
45 tlvdata,invoice_request,amount,msat,tu64, tlvtype,invoice_request,offer_absolute_expiry,14
46 tlvtype,invoice_request,features,12 tlvdata,invoice_request,offer_absolute_expiry,seconds_from_epoch,tu64,
47 tlvdata,invoice_request,features,features,byte,... tlvtype,invoice_request,offer_paths,16
48 tlvtype,invoice_request,quantity,32 tlvdata,invoice_request,offer_paths,paths,blinded_path,...
49 tlvdata,invoice_request,quantity,quantity,tu64, tlvtype,invoice_request,offer_issuer,18
50 tlvtype,invoice_request,recurrence_counter,36 tlvdata,invoice_request,offer_issuer,issuer,utf8,...
51 tlvdata,invoice_request,recurrence_counter,counter,tu32, tlvtype,invoice_request,offer_quantity_max,20
52 tlvtype,invoice_request,recurrence_start,68 tlvdata,invoice_request,offer_quantity_max,max,tu64,
53 tlvdata,invoice_request,recurrence_start,period_offset,tu32, tlvtype,invoice_request,offer_node_id,22
54 tlvtype,invoice_request,payer_key,38 tlvdata,invoice_request,offer_node_id,node_id,point,
55 tlvdata,invoice_request,payer_key,key,point, tlvtype,invoice_request,offer_recurrence,26
56 tlvtype,invoice_request,payer_note,39 tlvdata,invoice_request,offer_recurrence,recurrence,recurrence,
57 tlvdata,invoice_request,payer_note,note,utf8,... tlvtype,invoice_request,offer_recurrence_paywindow,28
58 tlvtype,invoice_request,payer_info,50 tlvdata,invoice_request,offer_recurrence_paywindow,paywindow,recurrence_paywindow,
59 tlvdata,invoice_request,payer_info,blob,byte,... tlvtype,invoice_request,offer_recurrence_limit,30
60 tlvtype,invoice_request,replace_invoice,56 tlvdata,invoice_request,offer_recurrence_limit,max_period,tu32,
61 tlvdata,invoice_request,replace_invoice,payment_hash,sha256, tlvtype,invoice_request,offer_recurrence_base,32
62 tlvdata,invoice_request,offer_recurrence_base,base,recurrence_base,
63 tlvtype,invoice_request,invreq_chain,80
64 tlvdata,invoice_request,invreq_chain,chain,chain_hash,
65 tlvtype,invoice_request,invreq_amount,82
66 tlvdata,invoice_request,invreq_amount,msat,tu64,
67 tlvtype,invoice_request,invreq_features,84
68 tlvdata,invoice_request,invreq_features,features,byte,...
69 tlvtype,invoice_request,invreq_quantity,86
70 tlvdata,invoice_request,invreq_quantity,quantity,tu64,
71 tlvtype,invoice_request,invreq_payer_id,88
72 tlvdata,invoice_request,invreq_payer_id,key,point,
73 tlvtype,invoice_request,invreq_payer_note,89
74 tlvdata,invoice_request,invreq_payer_note,note,utf8,...
75 tlvtype,invoice_request,invreq_recurrence_counter,90
76 tlvdata,invoice_request,invreq_recurrence_counter,counter,tu32,
77 tlvtype,invoice_request,invreq_recurrence_start,92
78 tlvdata,invoice_request,invreq_recurrence_start,period_offset,tu32,
79 tlvtype,invoice_request,signature,240 tlvtype,invoice_request,signature,240
80 tlvdata,invoice_request,signature,sig,bip340sig, tlvdata,invoice_request,signature,sig,bip340sig,
81 tlvtype,invoice,chain,3 tlvtype,invoice,invreq_metadata,0
82 tlvdata,invoice,chain,chain,chain_hash, tlvdata,invoice,invreq_metadata,blob,byte,...
83 tlvtype,invoice,offer_id,4 tlvtype,invoice,offer_chains,2
84 tlvdata,invoice,offer_id,offer_id,sha256, tlvdata,invoice,offer_chains,chains,chain_hash,...
85 tlvtype,invoice,amount,8 tlvtype,invoice,offer_metadata,4
86 tlvdata,invoice,amount,msat,tu64, tlvdata,invoice,offer_metadata,data,byte,...
87 tlvtype,invoice,description,10 tlvtype,invoice,offer_currency,6
88 tlvdata,invoice,description,description,utf8,... tlvdata,invoice,offer_currency,iso4217,utf8,...
89 tlvtype,invoice,features,12 tlvtype,invoice,offer_amount,8
90 tlvdata,invoice,features,features,byte,... tlvdata,invoice,offer_amount,amount,tu64,
91 tlvtype,invoice,paths,16 tlvtype,invoice,offer_description,10
92 tlvdata,invoice,paths,paths,blinded_path,... tlvdata,invoice,offer_description,description,utf8,...
93 tlvtype,invoice,blindedpay,18 tlvtype,invoice,offer_features,12
94 tlvdata,invoice,blindedpay,payinfo,blinded_payinfo,... tlvdata,invoice,offer_features,features,byte,...
95 tlvtype,invoice,blinded_capacities,19 tlvtype,invoice,offer_absolute_expiry,14
96 tlvdata,invoice,blinded_capacities,incoming_msat,u64,... tlvdata,invoice,offer_absolute_expiry,seconds_from_epoch,tu64,
97 tlvtype,invoice,issuer,20 tlvtype,invoice,offer_paths,16
98 tlvdata,invoice,issuer,issuer,utf8,... tlvdata,invoice,offer_paths,paths,blinded_path,...
99 tlvtype,invoice,node_id,30 tlvtype,invoice,offer_issuer,18
100 tlvdata,invoice,node_id,node_id,point, tlvdata,invoice,offer_issuer,issuer,utf8,...
101 tlvtype,invoice,quantity,32 tlvtype,invoice,offer_quantity_max,20
102 tlvdata,invoice,quantity,quantity,tu64, tlvdata,invoice,offer_quantity_max,max,tu64,
103 tlvtype,invoice,refund_for,34 tlvtype,invoice,offer_node_id,22
104 tlvdata,invoice,refund_for,refunded_payment_hash,sha256, tlvdata,invoice,offer_node_id,node_id,point,
105 tlvtype,invoice,recurrence_counter,36 tlvtype,invoice,offer_recurrence,26
106 tlvdata,invoice,recurrence_counter,counter,tu32, tlvdata,invoice,offer_recurrence,recurrence,recurrence,
107 tlvtype,invoice,recurrence_start,68 tlvtype,invoice,offer_recurrence_paywindow,28
108 tlvdata,invoice,recurrence_start,period_offset,tu32, tlvdata,invoice,offer_recurrence_paywindow,paywindow,recurrence_paywindow,
109 tlvtype,invoice,recurrence_basetime,64 tlvtype,invoice,offer_recurrence_limit,30
110 tlvdata,invoice,recurrence_basetime,basetime,tu64, tlvdata,invoice,offer_recurrence_limit,max_period,tu32,
111 tlvtype,invoice,payer_key,38 tlvtype,invoice,offer_recurrence_base,32
112 tlvdata,invoice,payer_key,key,point, tlvdata,invoice,offer_recurrence_base,base,recurrence_base,
113 tlvtype,invoice,payer_note,39 tlvtype,invoice,invreq_chain,80
114 tlvdata,invoice,payer_note,note,utf8,... tlvdata,invoice,invreq_chain,chain,chain_hash,
115 tlvtype,invoice,created_at,40 tlvtype,invoice,invreq_amount,82
116 tlvdata,invoice,created_at,timestamp,tu64, tlvdata,invoice,invreq_amount,msat,tu64,
117 tlvtype,invoice,payment_hash,42 tlvtype,invoice,invreq_features,84
118 tlvdata,invoice,payment_hash,payment_hash,sha256, tlvdata,invoice,invreq_features,features,byte,...
119 tlvtype,invoice,relative_expiry,44 tlvtype,invoice,invreq_quantity,86
120 tlvdata,invoice,relative_expiry,seconds_from_creation,tu32, tlvdata,invoice,invreq_quantity,quantity,tu64,
121 tlvtype,invoice,cltv,46 tlvtype,invoice,invreq_payer_id,88
122 tlvdata,invoice,cltv,min_final_cltv_expiry,tu16, tlvdata,invoice,invreq_payer_id,key,point,
123 tlvtype,invoice,fallbacks,48 tlvtype,invoice,invreq_payer_note,89
124 tlvdata,invoice,fallbacks,fallbacks,fallback_address,... tlvdata,invoice,invreq_payer_note,note,utf8,...
125 tlvtype,invoice,payer_info,50 tlvtype,invoice,invreq_recurrence_counter,90
126 tlvdata,invoice,payer_info,blob,byte,... tlvdata,invoice,invreq_recurrence_counter,counter,tu32,
127 tlvtype,invoice,refund_signature,52 tlvtype,invoice,invreq_recurrence_start,92
128 tlvdata,invoice,refund_signature,payer_signature,bip340sig, tlvdata,invoice,invreq_recurrence_start,period_offset,tu32,
129 tlvtype,invoice,send_invoice,54 tlvtype,invoice,invoice_paths,160
130 tlvtype,invoice,replace_invoice,56 tlvdata,invoice,invoice_paths,paths,blinded_path,...
131 tlvdata,invoice,replace_invoice,payment_hash,sha256, tlvtype,invoice,invoice_blindedpay,162
132 tlvdata,invoice,invoice_blindedpay,payinfo,blinded_payinfo,...
133 tlvtype,invoice,invoice_created_at,164
134 tlvdata,invoice,invoice_created_at,timestamp,tu64,
135 tlvtype,invoice,invoice_relative_expiry,166
136 tlvdata,invoice,invoice_relative_expiry,seconds_from_creation,tu32,
137 tlvtype,invoice,invoice_payment_hash,168
138 tlvdata,invoice,invoice_payment_hash,payment_hash,sha256,
139 tlvtype,invoice,invoice_amount,170
140 tlvdata,invoice,invoice_amount,msat,tu64,
141 tlvtype,invoice,invoice_fallbacks,172
142 tlvdata,invoice,invoice_fallbacks,fallbacks,fallback_address,...
143 tlvtype,invoice,invoice_features,174
144 tlvdata,invoice,invoice_features,features,byte,...
145 tlvtype,invoice,invoice_node_id,176
146 tlvdata,invoice,invoice_node_id,node_id,point,
147 tlvtype,invoice,invoice_recurrence_basetime,178
148 tlvdata,invoice,invoice_recurrence_basetime,basetime,tu64,
149 tlvtype,invoice,signature,240 tlvtype,invoice,signature,240
150 tlvdata,invoice,signature,sig,bip340sig, tlvdata,invoice,signature,sig,bip340sig,
151 subtype,recurrence
152 subtypedata,recurrence,time_unit,byte,
153 subtypedata,recurrence,period,tu32,
154 subtype,recurrence_paywindow
155 subtypedata,recurrence_paywindow,seconds_before,u32,
156 subtypedata,recurrence_paywindow,proportional_amount,byte,
157 subtypedata,recurrence_paywindow,seconds_after,tu32,
158 subtype,recurrence_base
159 subtypedata,recurrence_base,start_any_period,byte,
160 subtypedata,recurrence_base,basetime,tu64,
161 subtype,blinded_payinfo subtype,blinded_payinfo
162 subtypedata,blinded_payinfo,fee_base_msat,u32, subtypedata,blinded_payinfo,fee_base_msat,u32,
163 subtypedata,blinded_payinfo,fee_proportional_millionths,u32, subtypedata,blinded_payinfo,fee_proportional_millionths,u32,

View File

@@ -1,119 +1,163 @@
tlvtype,offer,chains,2 tlvtype,offer,offer_chains,2
tlvdata,offer,chains,chains,chain_hash,... tlvdata,offer,offer_chains,chains,chain_hash,...
tlvtype,offer,currency,6 tlvtype,offer,offer_metadata,4
tlvdata,offer,currency,iso4217,utf8,... tlvdata,offer,offer_metadata,data,byte,...
tlvtype,offer,amount,8 tlvtype,offer,offer_currency,6
tlvdata,offer,amount,amount,tu64, tlvdata,offer,offer_currency,iso4217,utf8,...
tlvtype,offer,description,10 tlvtype,offer,offer_amount,8
tlvdata,offer,description,description,utf8,... tlvdata,offer,offer_amount,amount,tu64,
tlvtype,offer,features,12 tlvtype,offer,offer_description,10
tlvdata,offer,features,features,byte,... tlvdata,offer,offer_description,description,utf8,...
tlvtype,offer,absolute_expiry,14 tlvtype,offer,offer_features,12
tlvdata,offer,absolute_expiry,seconds_from_epoch,tu64, tlvdata,offer,offer_features,features,byte,...
tlvtype,offer,paths,16 tlvtype,offer,offer_absolute_expiry,14
tlvdata,offer,paths,paths,blinded_path,... tlvdata,offer,offer_absolute_expiry,seconds_from_epoch,tu64,
tlvtype,offer,issuer,20 tlvtype,offer,offer_paths,16
tlvdata,offer,issuer,issuer,utf8,... tlvdata,offer,offer_paths,paths,blinded_path,...
tlvtype,offer,quantity_min,22 tlvtype,offer,offer_issuer,18
tlvdata,offer,quantity_min,min,tu64, tlvdata,offer,offer_issuer,issuer,utf8,...
tlvtype,offer,quantity_max,24 tlvtype,offer,offer_quantity_max,20
tlvdata,offer,quantity_max,max,tu64, tlvdata,offer,offer_quantity_max,max,tu64,
tlvtype,offer,recurrence,26 tlvtype,offer,offer_node_id,22
tlvdata,offer,recurrence,time_unit,byte, tlvdata,offer,offer_node_id,node_id,point,
tlvdata,offer,recurrence,period,tu32, tlvtype,offer,offer_recurrence,26
tlvtype,offer,recurrence_paywindow,64 tlvdata,offer,offer_recurrence,recurrence,recurrence,
tlvdata,offer,recurrence_paywindow,seconds_before,u32, tlvtype,offer,offer_recurrence_paywindow,28
tlvdata,offer,recurrence_paywindow,proportional_amount,byte, tlvdata,offer,offer_recurrence_paywindow,paywindow,recurrence_paywindow,
tlvdata,offer,recurrence_paywindow,seconds_after,tu32, tlvtype,offer,offer_recurrence_limit,30
tlvtype,offer,recurrence_limit,66 tlvdata,offer,offer_recurrence_limit,max_period,tu32,
tlvdata,offer,recurrence_limit,max_period,tu32, tlvtype,offer,offer_recurrence_base,32
tlvtype,offer,recurrence_base,28 tlvdata,offer,offer_recurrence_base,base,recurrence_base,
tlvdata,offer,recurrence_base,start_any_period,byte, tlvtype,invoice_request,invreq_metadata,0
tlvdata,offer,recurrence_base,basetime,tu64, tlvdata,invoice_request,invreq_metadata,blob,byte,...
tlvtype,offer,node_id,30 tlvtype,invoice_request,offer_chains,2
tlvdata,offer,node_id,node_id,point, tlvdata,invoice_request,offer_chains,chains,chain_hash,...
tlvtype,offer,send_invoice,54 tlvtype,invoice_request,offer_metadata,4
tlvtype,offer,refund_for,34 tlvdata,invoice_request,offer_metadata,data,byte,...
tlvdata,offer,refund_for,refunded_payment_hash,sha256, tlvtype,invoice_request,offer_currency,6
tlvtype,offer,signature,240 tlvdata,invoice_request,offer_currency,iso4217,utf8,...
tlvdata,offer,signature,sig,bip340sig, tlvtype,invoice_request,offer_amount,8
tlvtype,invoice_request,chain,3 tlvdata,invoice_request,offer_amount,amount,tu64,
tlvdata,invoice_request,chain,chain,chain_hash, tlvtype,invoice_request,offer_description,10
tlvtype,invoice_request,offer_id,4 tlvdata,invoice_request,offer_description,description,utf8,...
tlvdata,invoice_request,offer_id,offer_id,sha256, tlvtype,invoice_request,offer_features,12
tlvtype,invoice_request,amount,8 tlvdata,invoice_request,offer_features,features,byte,...
tlvdata,invoice_request,amount,msat,tu64, tlvtype,invoice_request,offer_absolute_expiry,14
tlvtype,invoice_request,features,12 tlvdata,invoice_request,offer_absolute_expiry,seconds_from_epoch,tu64,
tlvdata,invoice_request,features,features,byte,... tlvtype,invoice_request,offer_paths,16
tlvtype,invoice_request,quantity,32 tlvdata,invoice_request,offer_paths,paths,blinded_path,...
tlvdata,invoice_request,quantity,quantity,tu64, tlvtype,invoice_request,offer_issuer,18
tlvtype,invoice_request,recurrence_counter,36 tlvdata,invoice_request,offer_issuer,issuer,utf8,...
tlvdata,invoice_request,recurrence_counter,counter,tu32, tlvtype,invoice_request,offer_quantity_max,20
tlvtype,invoice_request,recurrence_start,68 tlvdata,invoice_request,offer_quantity_max,max,tu64,
tlvdata,invoice_request,recurrence_start,period_offset,tu32, tlvtype,invoice_request,offer_node_id,22
tlvtype,invoice_request,payer_key,38 tlvdata,invoice_request,offer_node_id,node_id,point,
tlvdata,invoice_request,payer_key,key,point, tlvtype,invoice_request,offer_recurrence,26
tlvtype,invoice_request,payer_note,39 tlvdata,invoice_request,offer_recurrence,recurrence,recurrence,
tlvdata,invoice_request,payer_note,note,utf8,... tlvtype,invoice_request,offer_recurrence_paywindow,28
tlvtype,invoice_request,payer_info,50 tlvdata,invoice_request,offer_recurrence_paywindow,paywindow,recurrence_paywindow,
tlvdata,invoice_request,payer_info,blob,byte,... tlvtype,invoice_request,offer_recurrence_limit,30
tlvtype,invoice_request,replace_invoice,56 tlvdata,invoice_request,offer_recurrence_limit,max_period,tu32,
tlvdata,invoice_request,replace_invoice,payment_hash,sha256, 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 tlvtype,invoice_request,signature,240
tlvdata,invoice_request,signature,sig,bip340sig, tlvdata,invoice_request,signature,sig,bip340sig,
tlvtype,invoice,chain,3 tlvtype,invoice,invreq_metadata,0
tlvdata,invoice,chain,chain,chain_hash, tlvdata,invoice,invreq_metadata,blob,byte,...
tlvtype,invoice,offer_id,4 tlvtype,invoice,offer_chains,2
tlvdata,invoice,offer_id,offer_id,sha256, tlvdata,invoice,offer_chains,chains,chain_hash,...
tlvtype,invoice,amount,8 tlvtype,invoice,offer_metadata,4
tlvdata,invoice,amount,msat,tu64, tlvdata,invoice,offer_metadata,data,byte,...
tlvtype,invoice,description,10 tlvtype,invoice,offer_currency,6
tlvdata,invoice,description,description,utf8,... tlvdata,invoice,offer_currency,iso4217,utf8,...
tlvtype,invoice,features,12 tlvtype,invoice,offer_amount,8
tlvdata,invoice,features,features,byte,... tlvdata,invoice,offer_amount,amount,tu64,
tlvtype,invoice,paths,16 tlvtype,invoice,offer_description,10
tlvdata,invoice,paths,paths,blinded_path,... tlvdata,invoice,offer_description,description,utf8,...
tlvtype,invoice,blindedpay,18 tlvtype,invoice,offer_features,12
tlvdata,invoice,blindedpay,payinfo,blinded_payinfo,... tlvdata,invoice,offer_features,features,byte,...
tlvtype,invoice,blinded_capacities,19 tlvtype,invoice,offer_absolute_expiry,14
tlvdata,invoice,blinded_capacities,incoming_msat,u64,... tlvdata,invoice,offer_absolute_expiry,seconds_from_epoch,tu64,
tlvtype,invoice,issuer,20 tlvtype,invoice,offer_paths,16
tlvdata,invoice,issuer,issuer,utf8,... tlvdata,invoice,offer_paths,paths,blinded_path,...
tlvtype,invoice,node_id,30 tlvtype,invoice,offer_issuer,18
tlvdata,invoice,node_id,node_id,point, tlvdata,invoice,offer_issuer,issuer,utf8,...
tlvtype,invoice,quantity,32 tlvtype,invoice,offer_quantity_max,20
tlvdata,invoice,quantity,quantity,tu64, tlvdata,invoice,offer_quantity_max,max,tu64,
tlvtype,invoice,refund_for,34 tlvtype,invoice,offer_node_id,22
tlvdata,invoice,refund_for,refunded_payment_hash,sha256, tlvdata,invoice,offer_node_id,node_id,point,
tlvtype,invoice,recurrence_counter,36 tlvtype,invoice,offer_recurrence,26
tlvdata,invoice,recurrence_counter,counter,tu32, tlvdata,invoice,offer_recurrence,recurrence,recurrence,
tlvtype,invoice,recurrence_start,68 tlvtype,invoice,offer_recurrence_paywindow,28
tlvdata,invoice,recurrence_start,period_offset,tu32, tlvdata,invoice,offer_recurrence_paywindow,paywindow,recurrence_paywindow,
tlvtype,invoice,recurrence_basetime,64 tlvtype,invoice,offer_recurrence_limit,30
tlvdata,invoice,recurrence_basetime,basetime,tu64, tlvdata,invoice,offer_recurrence_limit,max_period,tu32,
tlvtype,invoice,payer_key,38 tlvtype,invoice,offer_recurrence_base,32
tlvdata,invoice,payer_key,key,point, tlvdata,invoice,offer_recurrence_base,base,recurrence_base,
tlvtype,invoice,payer_note,39 tlvtype,invoice,invreq_chain,80
tlvdata,invoice,payer_note,note,utf8,... tlvdata,invoice,invreq_chain,chain,chain_hash,
tlvtype,invoice,created_at,40 tlvtype,invoice,invreq_amount,82
tlvdata,invoice,created_at,timestamp,tu64, tlvdata,invoice,invreq_amount,msat,tu64,
tlvtype,invoice,payment_hash,42 tlvtype,invoice,invreq_features,84
tlvdata,invoice,payment_hash,payment_hash,sha256, tlvdata,invoice,invreq_features,features,byte,...
tlvtype,invoice,relative_expiry,44 tlvtype,invoice,invreq_quantity,86
tlvdata,invoice,relative_expiry,seconds_from_creation,tu32, tlvdata,invoice,invreq_quantity,quantity,tu64,
tlvtype,invoice,cltv,46 tlvtype,invoice,invreq_payer_id,88
tlvdata,invoice,cltv,min_final_cltv_expiry,tu16, tlvdata,invoice,invreq_payer_id,key,point,
tlvtype,invoice,fallbacks,48 tlvtype,invoice,invreq_payer_note,89
tlvdata,invoice,fallbacks,fallbacks,fallback_address,... tlvdata,invoice,invreq_payer_note,note,utf8,...
tlvtype,invoice,payer_info,50 tlvtype,invoice,invreq_recurrence_counter,90
tlvdata,invoice,payer_info,blob,byte,... tlvdata,invoice,invreq_recurrence_counter,counter,tu32,
tlvtype,invoice,refund_signature,52 tlvtype,invoice,invreq_recurrence_start,92
tlvdata,invoice,refund_signature,payer_signature,bip340sig, tlvdata,invoice,invreq_recurrence_start,period_offset,tu32,
tlvtype,invoice,send_invoice,54 tlvtype,invoice,invoice_paths,160
tlvtype,invoice,replace_invoice,56 tlvdata,invoice,invoice_paths,paths,blinded_path,...
tlvdata,invoice,replace_invoice,payment_hash,sha256, 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 tlvtype,invoice,signature,240
tlvdata,invoice,signature,sig,bip340sig, 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 subtype,blinded_payinfo
subtypedata,blinded_payinfo,fee_base_msat,u32, subtypedata,blinded_payinfo,fee_base_msat,u32,
subtypedata,blinded_payinfo,fee_proportional_millionths,u32, subtypedata,blinded_payinfo,fee_proportional_millionths,u32,
1 tlvtype,offer,chains,2 tlvtype,offer,offer_chains,2
2 tlvdata,offer,chains,chains,chain_hash,... tlvdata,offer,offer_chains,chains,chain_hash,...
3 tlvtype,offer,currency,6 tlvtype,offer,offer_metadata,4
4 tlvdata,offer,currency,iso4217,utf8,... tlvdata,offer,offer_metadata,data,byte,...
5 tlvtype,offer,amount,8 tlvtype,offer,offer_currency,6
6 tlvdata,offer,amount,amount,tu64, tlvdata,offer,offer_currency,iso4217,utf8,...
7 tlvtype,offer,description,10 tlvtype,offer,offer_amount,8
8 tlvdata,offer,description,description,utf8,... tlvdata,offer,offer_amount,amount,tu64,
9 tlvtype,offer,features,12 tlvtype,offer,offer_description,10
10 tlvdata,offer,features,features,byte,... tlvdata,offer,offer_description,description,utf8,...
11 tlvtype,offer,absolute_expiry,14 tlvtype,offer,offer_features,12
12 tlvdata,offer,absolute_expiry,seconds_from_epoch,tu64, tlvdata,offer,offer_features,features,byte,...
13 tlvtype,offer,paths,16 tlvtype,offer,offer_absolute_expiry,14
14 tlvdata,offer,paths,paths,blinded_path,... tlvdata,offer,offer_absolute_expiry,seconds_from_epoch,tu64,
15 tlvtype,offer,issuer,20 tlvtype,offer,offer_paths,16
16 tlvdata,offer,issuer,issuer,utf8,... tlvdata,offer,offer_paths,paths,blinded_path,...
17 tlvtype,offer,quantity_min,22 tlvtype,offer,offer_issuer,18
18 tlvdata,offer,quantity_min,min,tu64, tlvdata,offer,offer_issuer,issuer,utf8,...
19 tlvtype,offer,quantity_max,24 tlvtype,offer,offer_quantity_max,20
20 tlvdata,offer,quantity_max,max,tu64, tlvdata,offer,offer_quantity_max,max,tu64,
21 tlvtype,offer,recurrence,26 tlvtype,offer,offer_node_id,22
22 tlvdata,offer,recurrence,time_unit,byte, tlvdata,offer,offer_node_id,node_id,point,
23 tlvdata,offer,recurrence,period,tu32, tlvtype,offer,offer_recurrence,26
24 tlvtype,offer,recurrence_paywindow,64 tlvdata,offer,offer_recurrence,recurrence,recurrence,
25 tlvdata,offer,recurrence_paywindow,seconds_before,u32, tlvtype,offer,offer_recurrence_paywindow,28
26 tlvdata,offer,recurrence_paywindow,proportional_amount,byte, tlvdata,offer,offer_recurrence_paywindow,paywindow,recurrence_paywindow,
27 tlvdata,offer,recurrence_paywindow,seconds_after,tu32, tlvtype,offer,offer_recurrence_limit,30
28 tlvtype,offer,recurrence_limit,66 tlvdata,offer,offer_recurrence_limit,max_period,tu32,
29 tlvdata,offer,recurrence_limit,max_period,tu32, tlvtype,offer,offer_recurrence_base,32
30 tlvtype,offer,recurrence_base,28 tlvdata,offer,offer_recurrence_base,base,recurrence_base,
31 tlvdata,offer,recurrence_base,start_any_period,byte, tlvtype,invoice_request,invreq_metadata,0
32 tlvdata,offer,recurrence_base,basetime,tu64, tlvdata,invoice_request,invreq_metadata,blob,byte,...
33 tlvtype,offer,node_id,30 tlvtype,invoice_request,offer_chains,2
34 tlvdata,offer,node_id,node_id,point, tlvdata,invoice_request,offer_chains,chains,chain_hash,...
35 tlvtype,offer,send_invoice,54 tlvtype,invoice_request,offer_metadata,4
36 tlvtype,offer,refund_for,34 tlvdata,invoice_request,offer_metadata,data,byte,...
37 tlvdata,offer,refund_for,refunded_payment_hash,sha256, tlvtype,invoice_request,offer_currency,6
38 tlvtype,offer,signature,240 tlvdata,invoice_request,offer_currency,iso4217,utf8,...
39 tlvdata,offer,signature,sig,bip340sig, tlvtype,invoice_request,offer_amount,8
40 tlvtype,invoice_request,chain,3 tlvdata,invoice_request,offer_amount,amount,tu64,
41 tlvdata,invoice_request,chain,chain,chain_hash, tlvtype,invoice_request,offer_description,10
42 tlvtype,invoice_request,offer_id,4 tlvdata,invoice_request,offer_description,description,utf8,...
43 tlvdata,invoice_request,offer_id,offer_id,sha256, tlvtype,invoice_request,offer_features,12
44 tlvtype,invoice_request,amount,8 tlvdata,invoice_request,offer_features,features,byte,...
45 tlvdata,invoice_request,amount,msat,tu64, tlvtype,invoice_request,offer_absolute_expiry,14
46 tlvtype,invoice_request,features,12 tlvdata,invoice_request,offer_absolute_expiry,seconds_from_epoch,tu64,
47 tlvdata,invoice_request,features,features,byte,... tlvtype,invoice_request,offer_paths,16
48 tlvtype,invoice_request,quantity,32 tlvdata,invoice_request,offer_paths,paths,blinded_path,...
49 tlvdata,invoice_request,quantity,quantity,tu64, tlvtype,invoice_request,offer_issuer,18
50 tlvtype,invoice_request,recurrence_counter,36 tlvdata,invoice_request,offer_issuer,issuer,utf8,...
51 tlvdata,invoice_request,recurrence_counter,counter,tu32, tlvtype,invoice_request,offer_quantity_max,20
52 tlvtype,invoice_request,recurrence_start,68 tlvdata,invoice_request,offer_quantity_max,max,tu64,
53 tlvdata,invoice_request,recurrence_start,period_offset,tu32, tlvtype,invoice_request,offer_node_id,22
54 tlvtype,invoice_request,payer_key,38 tlvdata,invoice_request,offer_node_id,node_id,point,
55 tlvdata,invoice_request,payer_key,key,point, tlvtype,invoice_request,offer_recurrence,26
56 tlvtype,invoice_request,payer_note,39 tlvdata,invoice_request,offer_recurrence,recurrence,recurrence,
57 tlvdata,invoice_request,payer_note,note,utf8,... tlvtype,invoice_request,offer_recurrence_paywindow,28
58 tlvtype,invoice_request,payer_info,50 tlvdata,invoice_request,offer_recurrence_paywindow,paywindow,recurrence_paywindow,
59 tlvdata,invoice_request,payer_info,blob,byte,... tlvtype,invoice_request,offer_recurrence_limit,30
60 tlvtype,invoice_request,replace_invoice,56 tlvdata,invoice_request,offer_recurrence_limit,max_period,tu32,
61 tlvdata,invoice_request,replace_invoice,payment_hash,sha256, tlvtype,invoice_request,offer_recurrence_base,32
62 tlvdata,invoice_request,offer_recurrence_base,base,recurrence_base,
63 tlvtype,invoice_request,invreq_chain,80
64 tlvdata,invoice_request,invreq_chain,chain,chain_hash,
65 tlvtype,invoice_request,invreq_amount,82
66 tlvdata,invoice_request,invreq_amount,msat,tu64,
67 tlvtype,invoice_request,invreq_features,84
68 tlvdata,invoice_request,invreq_features,features,byte,...
69 tlvtype,invoice_request,invreq_quantity,86
70 tlvdata,invoice_request,invreq_quantity,quantity,tu64,
71 tlvtype,invoice_request,invreq_payer_id,88
72 tlvdata,invoice_request,invreq_payer_id,key,point,
73 tlvtype,invoice_request,invreq_payer_note,89
74 tlvdata,invoice_request,invreq_payer_note,note,utf8,...
75 tlvtype,invoice_request,invreq_recurrence_counter,90
76 tlvdata,invoice_request,invreq_recurrence_counter,counter,tu32,
77 tlvtype,invoice_request,invreq_recurrence_start,92
78 tlvdata,invoice_request,invreq_recurrence_start,period_offset,tu32,
79 tlvtype,invoice_request,signature,240 tlvtype,invoice_request,signature,240
80 tlvdata,invoice_request,signature,sig,bip340sig, tlvdata,invoice_request,signature,sig,bip340sig,
81 tlvtype,invoice,chain,3 tlvtype,invoice,invreq_metadata,0
82 tlvdata,invoice,chain,chain,chain_hash, tlvdata,invoice,invreq_metadata,blob,byte,...
83 tlvtype,invoice,offer_id,4 tlvtype,invoice,offer_chains,2
84 tlvdata,invoice,offer_id,offer_id,sha256, tlvdata,invoice,offer_chains,chains,chain_hash,...
85 tlvtype,invoice,amount,8 tlvtype,invoice,offer_metadata,4
86 tlvdata,invoice,amount,msat,tu64, tlvdata,invoice,offer_metadata,data,byte,...
87 tlvtype,invoice,description,10 tlvtype,invoice,offer_currency,6
88 tlvdata,invoice,description,description,utf8,... tlvdata,invoice,offer_currency,iso4217,utf8,...
89 tlvtype,invoice,features,12 tlvtype,invoice,offer_amount,8
90 tlvdata,invoice,features,features,byte,... tlvdata,invoice,offer_amount,amount,tu64,
91 tlvtype,invoice,paths,16 tlvtype,invoice,offer_description,10
92 tlvdata,invoice,paths,paths,blinded_path,... tlvdata,invoice,offer_description,description,utf8,...
93 tlvtype,invoice,blindedpay,18 tlvtype,invoice,offer_features,12
94 tlvdata,invoice,blindedpay,payinfo,blinded_payinfo,... tlvdata,invoice,offer_features,features,byte,...
95 tlvtype,invoice,blinded_capacities,19 tlvtype,invoice,offer_absolute_expiry,14
96 tlvdata,invoice,blinded_capacities,incoming_msat,u64,... tlvdata,invoice,offer_absolute_expiry,seconds_from_epoch,tu64,
97 tlvtype,invoice,issuer,20 tlvtype,invoice,offer_paths,16
98 tlvdata,invoice,issuer,issuer,utf8,... tlvdata,invoice,offer_paths,paths,blinded_path,...
99 tlvtype,invoice,node_id,30 tlvtype,invoice,offer_issuer,18
100 tlvdata,invoice,node_id,node_id,point, tlvdata,invoice,offer_issuer,issuer,utf8,...
101 tlvtype,invoice,quantity,32 tlvtype,invoice,offer_quantity_max,20
102 tlvdata,invoice,quantity,quantity,tu64, tlvdata,invoice,offer_quantity_max,max,tu64,
103 tlvtype,invoice,refund_for,34 tlvtype,invoice,offer_node_id,22
104 tlvdata,invoice,refund_for,refunded_payment_hash,sha256, tlvdata,invoice,offer_node_id,node_id,point,
105 tlvtype,invoice,recurrence_counter,36 tlvtype,invoice,offer_recurrence,26
106 tlvdata,invoice,recurrence_counter,counter,tu32, tlvdata,invoice,offer_recurrence,recurrence,recurrence,
107 tlvtype,invoice,recurrence_start,68 tlvtype,invoice,offer_recurrence_paywindow,28
108 tlvdata,invoice,recurrence_start,period_offset,tu32, tlvdata,invoice,offer_recurrence_paywindow,paywindow,recurrence_paywindow,
109 tlvtype,invoice,recurrence_basetime,64 tlvtype,invoice,offer_recurrence_limit,30
110 tlvdata,invoice,recurrence_basetime,basetime,tu64, tlvdata,invoice,offer_recurrence_limit,max_period,tu32,
111 tlvtype,invoice,payer_key,38 tlvtype,invoice,offer_recurrence_base,32
112 tlvdata,invoice,payer_key,key,point, tlvdata,invoice,offer_recurrence_base,base,recurrence_base,
113 tlvtype,invoice,payer_note,39 tlvtype,invoice,invreq_chain,80
114 tlvdata,invoice,payer_note,note,utf8,... tlvdata,invoice,invreq_chain,chain,chain_hash,
115 tlvtype,invoice,created_at,40 tlvtype,invoice,invreq_amount,82
116 tlvdata,invoice,created_at,timestamp,tu64, tlvdata,invoice,invreq_amount,msat,tu64,
117 tlvtype,invoice,payment_hash,42 tlvtype,invoice,invreq_features,84
118 tlvdata,invoice,payment_hash,payment_hash,sha256, tlvdata,invoice,invreq_features,features,byte,...
119 tlvtype,invoice,relative_expiry,44 tlvtype,invoice,invreq_quantity,86
120 tlvdata,invoice,relative_expiry,seconds_from_creation,tu32, tlvdata,invoice,invreq_quantity,quantity,tu64,
121 tlvtype,invoice,cltv,46 tlvtype,invoice,invreq_payer_id,88
122 tlvdata,invoice,cltv,min_final_cltv_expiry,tu16, tlvdata,invoice,invreq_payer_id,key,point,
123 tlvtype,invoice,fallbacks,48 tlvtype,invoice,invreq_payer_note,89
124 tlvdata,invoice,fallbacks,fallbacks,fallback_address,... tlvdata,invoice,invreq_payer_note,note,utf8,...
125 tlvtype,invoice,payer_info,50 tlvtype,invoice,invreq_recurrence_counter,90
126 tlvdata,invoice,payer_info,blob,byte,... tlvdata,invoice,invreq_recurrence_counter,counter,tu32,
127 tlvtype,invoice,refund_signature,52 tlvtype,invoice,invreq_recurrence_start,92
128 tlvdata,invoice,refund_signature,payer_signature,bip340sig, tlvdata,invoice,invreq_recurrence_start,period_offset,tu32,
129 tlvtype,invoice,send_invoice,54 tlvtype,invoice,invoice_paths,160
130 tlvtype,invoice,replace_invoice,56 tlvdata,invoice,invoice_paths,paths,blinded_path,...
131 tlvdata,invoice,replace_invoice,payment_hash,sha256, tlvtype,invoice,invoice_blindedpay,162
132 tlvdata,invoice,invoice_blindedpay,payinfo,blinded_payinfo,...
133 tlvtype,invoice,invoice_created_at,164
134 tlvdata,invoice,invoice_created_at,timestamp,tu64,
135 tlvtype,invoice,invoice_relative_expiry,166
136 tlvdata,invoice,invoice_relative_expiry,seconds_from_creation,tu32,
137 tlvtype,invoice,invoice_payment_hash,168
138 tlvdata,invoice,invoice_payment_hash,payment_hash,sha256,
139 tlvtype,invoice,invoice_amount,170
140 tlvdata,invoice,invoice_amount,msat,tu64,
141 tlvtype,invoice,invoice_fallbacks,172
142 tlvdata,invoice,invoice_fallbacks,fallbacks,fallback_address,...
143 tlvtype,invoice,invoice_features,174
144 tlvdata,invoice,invoice_features,features,byte,...
145 tlvtype,invoice,invoice_node_id,176
146 tlvdata,invoice,invoice_node_id,node_id,point,
147 tlvtype,invoice,invoice_recurrence_basetime,178
148 tlvdata,invoice,invoice_recurrence_basetime,basetime,tu64,
149 tlvtype,invoice,signature,240 tlvtype,invoice,signature,240
150 tlvdata,invoice,signature,sig,bip340sig, tlvdata,invoice,signature,sig,bip340sig,
151 subtype,recurrence
152 subtypedata,recurrence,time_unit,byte,
153 subtypedata,recurrence,period,tu32,
154 subtype,recurrence_paywindow
155 subtypedata,recurrence_paywindow,seconds_before,u32,
156 subtypedata,recurrence_paywindow,proportional_amount,byte,
157 subtypedata,recurrence_paywindow,seconds_after,tu32,
158 subtype,recurrence_base
159 subtypedata,recurrence_base,start_any_period,byte,
160 subtypedata,recurrence_base,basetime,tu64,
161 subtype,blinded_payinfo subtype,blinded_payinfo
162 subtypedata,blinded_payinfo,fee_base_msat,u32, subtypedata,blinded_payinfo,fee_base_msat,u32,
163 subtypedata,blinded_payinfo,fee_proportional_millionths,u32, subtypedata,blinded_payinfo,fee_proportional_millionths,u32,

View File

@@ -1,47 +1,87 @@
diff --git b/wire/bolt12_wire.csv a/wire/bolt12_wire.csv --- wire/bolt12_wire.csv.raw 2022-10-04 13:26:18.105307201 +1030
index 726c3c0a1..a53ca3cdf 100644 +++ wire/bolt12_wire.csv 2022-10-04 13:25:59.617242667 +1030
--- b/wire/bolt12_wire.csv @@ -22,6 +22,14 @@
+++ a/wire/bolt12_wire.csv tlvdata,offer,offer_quantity_max,max,tu64,
@@ -18,6 +18,18 @@ tlvtype,offer,quantity_min,22 tlvtype,offer,offer_node_id,24
tlvdata,offer,quantity_min,min,tu64, tlvdata,offer,offer_node_id,node_id,point,
tlvtype,offer,quantity_max,24 +tlvtype,offer,offer_recurrence,26
tlvdata,offer,quantity_max,max,tu64, +tlvdata,offer,offer_recurrence,recurrence,recurrence,
+tlvtype,offer,recurrence,26 +tlvtype,offer,offer_recurrence_paywindow,28
+tlvdata,offer,recurrence,time_unit,byte, +tlvdata,offer,offer_recurrence_paywindow,paywindow,recurrence_paywindow,
+tlvdata,offer,recurrence,period,tu32, +tlvtype,offer,offer_recurrence_limit,30
+tlvtype,offer,recurrence_paywindow,64 +tlvdata,offer,offer_recurrence_limit,max_period,tu32,
+tlvdata,offer,recurrence_paywindow,seconds_before,u32, +tlvtype,offer,offer_recurrence_base,32
+tlvdata,offer,recurrence_paywindow,proportional_amount,byte, +tlvdata,offer,offer_recurrence_base,base,recurrence_base,
+tlvdata,offer,recurrence_paywindow,seconds_after,tu32, tlvtype,invoice_request,invreq_metadata,0
+tlvtype,offer,recurrence_limit,66 tlvdata,invoice_request,invreq_metadata,blob,byte,...
+tlvdata,offer,recurrence_limit,max_period,tu32, tlvtype,invoice_request,offer_chains,2
+tlvtype,offer,recurrence_base,28 @@ -48,6 +60,14 @@
+tlvdata,offer,recurrence_base,start_any_period,byte, tlvdata,invoice_request,offer_quantity_max,max,tu64,
+tlvdata,offer,recurrence_base,basetime,tu64, tlvtype,invoice_request,offer_node_id,24
tlvtype,offer,node_id,30 tlvdata,invoice_request,offer_node_id,node_id,point,
tlvdata,offer,node_id,node_id,pubkey, +tlvtype,invoice_request,offer_recurrence,26
tlvtype,offer,send_invoice,54 +tlvdata,invoice_request,offer_recurrence,recurrence,recurrence,
@@ -40,6 +54,10 @@ tlvtype,invoice_request,features,12 +tlvtype,invoice_request,offer_recurrence_paywindow,28
tlvdata,invoice_request,features,features,byte,... +tlvdata,invoice_request,offer_recurrence_paywindow,paywindow,recurrence_paywindow,
tlvtype,invoice_request,quantity,32 +tlvtype,invoice_request,offer_recurrence_limit,30
tlvdata,invoice_request,quantity,quantity,tu64, +tlvdata,invoice_request,offer_recurrence_limit,max_period,tu32,
+tlvtype,invoice_request,recurrence_counter,36 +tlvtype,invoice_request,offer_recurrence_base,32
+tlvdata,invoice_request,recurrence_counter,counter,tu32, +tlvdata,invoice_request,offer_recurrence_base,base,recurrence_base,
+tlvtype,invoice_request,recurrence_start,68 tlvtype,invoice_request,invreq_chain,80
+tlvdata,invoice_request,recurrence_start,period_offset,tu32, tlvdata,invoice_request,invreq_chain,chain,chain_hash,
tlvtype,invoice_request,payer_key,38 tlvtype,invoice_request,invreq_amount,82
tlvdata,invoice_request,payer_key,key,pubkey, @@ -60,6 +84,10 @@
tlvtype,invoice_request,payer_note,39 tlvdata,invoice_request,invreq_payer_id,key,point,
@@ -74,6 +94,12 @@ tlvtype,invoice,quantity,32 tlvtype,invoice_request,invreq_payer_note,89
tlvdata,invoice,quantity,quantity,tu64, tlvdata,invoice_request,invreq_payer_note,note,utf8,...
tlvtype,invoice,refund_for,34 +tlvtype,invoice_request,invreq_recurrence_counter,90
tlvdata,invoice,refund_for,refunded_payment_hash,sha256, +tlvdata,invoice_request,invreq_recurrence_counter,counter,tu32,
+tlvtype,invoice,recurrence_counter,36 +tlvtype,invoice_request,invreq_recurrence_start,92
+tlvdata,invoice,recurrence_counter,counter,tu32, +tlvdata,invoice_request,invreq_recurrence_start,period_offset,tu32,
+tlvtype,invoice,recurrence_start,68 tlvtype,invoice_request,signature,240
+tlvdata,invoice,recurrence_start,period_offset,tu32, tlvdata,invoice_request,signature,sig,bip340sig,
+tlvtype,invoice,recurrence_basetime,64 tlvtype,invoice,invreq_metadata,0
+tlvdata,invoice,recurrence_basetime,basetime,tu64, @@ -89,5 +117,13 @@
tlvtype,invoice,payer_key,38 tlvtype,invoice,offer_node_id,24
tlvdata,invoice,payer_key,key,pubkey, tlvdata,invoice,offer_node_id,node_id,point,
tlvtype,invoice,payer_note,39 +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