common/bolt12: do more required checks in invoice_decode.

Rather than making the callers do this, make the invoice decoder perform
the various sanity checks.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2024-09-18 17:10:27 +09:30
parent 1b8a64f5e6
commit 1362448352
7 changed files with 132 additions and 72 deletions

View File

@ -2,11 +2,13 @@
#include <assert.h>
#include <bitcoin/chainparams.h>
#include <ccan/tal/str/str.h>
#include <ccan/time/time.h>
#include <common/bech32_util.h>
#include <common/bolt12.h>
#include <common/bolt12_merkle.h>
#include <common/configdir.h>
#include <common/features.h>
#include <common/overflows.h>
#include <inttypes.h>
#include <secp256k1_schnorrsig.h>
#include <time.h>
@ -324,11 +326,11 @@ char *invoice_encode(const tal_t *ctx, const struct tlv_invoice *invoice_tlv)
return to_bech32_charset(ctx, "lni", wire);
}
struct tlv_invoice *invoice_decode_nosig(const tal_t *ctx,
const char *b12, size_t b12len,
const struct feature_set *our_features,
const struct chainparams *must_be_chain,
char **fail)
struct tlv_invoice *invoice_decode_minimal(const tal_t *ctx,
const char *b12, size_t b12len,
const struct feature_set *our_features,
const struct chainparams *must_be_chain,
char **fail)
{
struct tlv_invoice *invoice;
const u8 *data;
@ -344,6 +346,17 @@ struct tlv_invoice *invoice_decode_nosig(const tal_t *ctx,
return NULL;
}
/* BOLT-offers #12:
* - if `invreq_chain` is not present:
* - MUST fail the request if bitcoin is not a supported chain.
* - otherwise:
* - MUST fail the request if `invreq_chain`.`chain` is not a
* supported chain.
* - if `invoice_features` contains unknown _odd_ bits that are non-zero:
* - MUST ignore the bit.
* - if `invoice_features` contains unknown _even_ bits that are non-zero:
* - MUST reject the invoice.
*/
*fail = check_features_and_chain(ctx,
our_features, must_be_chain,
invoice->invoice_features,
@ -476,17 +489,97 @@ struct tlv_invoice *invoice_decode(const tal_t *ctx,
char **fail)
{
struct tlv_invoice *invoice;
u64 expiry, now;
invoice = invoice_decode_nosig(ctx, b12, b12len, our_features,
must_be_chain, fail);
if (invoice) {
*fail = check_signature(ctx, invoice->fields,
"invoice", "signature",
invoice->invoice_node_id,
invoice->signature);
if (*fail)
invoice = tal_free(invoice);
invoice = invoice_decode_minimal(ctx, b12, b12len, our_features,
must_be_chain, fail);
if (!invoice)
return NULL;
*fail = check_signature(ctx, invoice->fields,
"invoice", "signature",
invoice->invoice_node_id,
invoice->signature);
if (*fail)
return tal_free(invoice);
/* BOLT-offers #12:
* A reader of an invoice:
* - MUST reject the invoice if `invoice_amount` 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.
* - MUST reject the invoice if `invoice_node_id` is not present.
*/
if (!invoice->invoice_amount) {
*fail = tal_strdup(ctx, "missing invoice_amount");
return tal_free(invoice);
}
if (!invoice->invoice_created_at) {
*fail = tal_strdup(ctx, "missing invoice_created_at");
return tal_free(invoice);
}
if (!invoice->invoice_payment_hash) {
*fail = tal_strdup(ctx, "missing invoice_payment_hash");
return tal_free(invoice);
}
if (!invoice->invoice_node_id) {
*fail = tal_strdup(ctx, "missing invoicenode_id");
return tal_free(invoice);
}
/* BOLT-offers #12:
* - if `invoice_relative_expiry` is present:
* - MUST reject the invoice if the current time since 1970-01-01 UTC
* is greater than `invoice_created_at` plus `seconds_from_creation`.
* - otherwise:
* - MUST reject the invoice if the current time since 1970-01-01 UTC
* is greater than `invoice_created_at` plus 7200.
*/
if (invoice->invoice_relative_expiry)
expiry = *invoice->invoice_relative_expiry;
else
expiry = 7200;
now = time_now().ts.tv_sec;
/* If it overflows, it's forever */
if (!add_overflows_u64(*invoice->invoice_created_at, expiry)
&& now > *invoice->invoice_created_at + expiry) {
*fail = tal_fmt(ctx, "expired %"PRIu64" seconds ago",
now - (*invoice->invoice_created_at + expiry));
return tal_free(invoice);
}
/* BOLT-offers #12:
* - MUST reject the invoice if `invoice_paths` is not present or is
* empty. */
if (tal_count(invoice->invoice_paths) == 0) {
*fail = tal_strdup(ctx, "missing/empty invoice_paths");
return tal_free(invoice);
}
/* BOLT-offers #12:
* - MUST reject the invoice if `num_hops` is 0 in any
* `blinded_path` in `invoice_paths`.
*/
for (size_t i = 0; i < tal_count(invoice->invoice_paths); i++) {
if (tal_count(invoice->invoice_paths[i]->path) != 0)
continue;
*fail = tal_fmt(ctx, "zero num_hops in path %zu/%zu",
i, tal_count(invoice->invoice_paths));
return tal_free(invoice);
}
/* BOLT-offers #12:
* - 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 (tal_count(invoice->invoice_blindedpay)
!= tal_count(invoice->invoice_paths)) {
*fail = tal_fmt(ctx, "expected %zu payinfo but found %zu",
tal_count(invoice->invoice_paths),
tal_count(invoice->invoice_blindedpay));
return tal_free(invoice);
}
return invoice;
}

View File

@ -71,7 +71,10 @@ char *invoice_encode(const tal_t *ctx, const struct tlv_invoice *bolt12_tlv);
* @must_be_chain: if non-NULL, chain to enforce.
* @fail: pointer to descriptive error string, set if this returns NULL.
*
* Note: checks signature!
* It checks it's well-formed (has amount, payment_hash, node_id, and
* is not expired). It also checks signature.
*
* Note: blinded path features need to be checked by the caller before use!
*/
struct tlv_invoice *invoice_decode(const tal_t *ctx,
const char *b12, size_t b12len,
@ -79,12 +82,12 @@ struct tlv_invoice *invoice_decode(const tal_t *ctx,
const struct chainparams *must_be_chain,
char **fail);
/* Variant which does not check signature */
struct tlv_invoice *invoice_decode_nosig(const tal_t *ctx,
const char *b12, size_t b12len,
const struct feature_set *our_features,
const struct chainparams *must_be_chain,
char **fail);
/* This one only checks it decides, and optionally is correct chain/features */
struct tlv_invoice *invoice_decode_minimal(const tal_t *ctx,
const char *b12, size_t b12len,
const struct feature_set *our_features,
const struct chainparams *must_be_chain,
char **fail);
/* Check a bolt12-style signature. */
bool bolt12_check_signature(const struct tlv_field *fields,

View File

@ -1732,9 +1732,9 @@ static struct command_result *json_createinvoice(struct command *cmd,
u32 expiry;
enum offer_status status;
inv = invoice_decode_nosig(cmd, invstring, strlen(invstring),
cmd->ld->our_features, chainparams,
&fail);
inv = invoice_decode_minimal(cmd, invstring, strlen(invstring),
cmd->ld->our_features, chainparams,
&fail);
if (!inv)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Unparsable invoice '%s': %s",

View File

@ -388,13 +388,13 @@ struct tlv_invoice *invoice_decode(const tal_t *ctx UNNEEDED,
const struct chainparams *must_be_chain UNNEEDED,
char **fail UNNEEDED)
{ fprintf(stderr, "invoice_decode called!\n"); abort(); }
/* Generated stub for invoice_decode_nosig */
struct tlv_invoice *invoice_decode_nosig(const tal_t *ctx UNNEEDED,
const char *b12 UNNEEDED, size_t b12len UNNEEDED,
const struct feature_set *our_features UNNEEDED,
const struct chainparams *must_be_chain UNNEEDED,
char **fail UNNEEDED)
{ fprintf(stderr, "invoice_decode_nosig called!\n"); abort(); }
/* Generated stub for invoice_decode_minimal */
struct tlv_invoice *invoice_decode_minimal(const tal_t *ctx UNNEEDED,
const char *b12 UNNEEDED, size_t b12len UNNEEDED,
const struct feature_set *our_features UNNEEDED,
const struct chainparams *must_be_chain UNNEEDED,
char **fail UNNEEDED)
{ fprintf(stderr, "invoice_decode_minimal called!\n"); abort(); }
/* Generated stub for invoice_encode */
char *invoice_encode(const tal_t *ctx UNNEEDED, const struct tlv_invoice *bolt12_tlv UNNEEDED)
{ fprintf(stderr, "invoice_encode called!\n"); abort(); }

View File

@ -1173,10 +1173,10 @@ static char *fetch_out_desc_invstr(const tal_t *ctx, const char *buf,
JSON_SCAN_TAL(ctx, json_strdup, &bolt))) {
struct tlv_invoice *bolt12;
bolt12 = invoice_decode_nosig(ctx, bolt, strlen(bolt),
/* No features/chain checks */
NULL, NULL,
&fail);
bolt12 = invoice_decode_minimal(ctx, bolt, strlen(bolt),
/* No features/chain checks */
NULL, NULL,
&fail);
if (!bolt12) {
*err = tal_fmt(ctx, "failed to parse"
" bolt12 %s: %s",

View File

@ -1310,18 +1310,6 @@ static struct command_result *json_pay(struct command *cmd,
/* p->features = tal_steal(p, b12->features); */
p->features = NULL;
if (!b12->invoice_node_id)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"invoice missing node_id");
if (!b12->invoice_payment_hash)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"invoice missing payment_hash");
if (!b12->invoice_created_at)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"invoice missing created_at");
if (!b12->invoice_amount)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"invoice missing invoice_amount");
invmsat = tal(cmd, struct amount_msat);
*invmsat = amount_msat(*b12->invoice_amount);
@ -1334,30 +1322,6 @@ static struct command_result *json_pay(struct command *cmd,
cmd, JSONRPC2_INVALID_PARAMS,
"recurring invoice requires a label");
/* BOLT-offers #12:
* - MUST reject the invoice if `invoice_paths` is not present
* or is empty.
*/
if (tal_count(b12->invoice_paths) == 0)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"invoice missing invoice_paths");
if (tal_count(b12->invoice_paths[0]->path) < 1)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"empty invoice_path[0]");
/* BOLT-offers #12:
* - MUST reject the invoice if `invoice_blindedpay` does not
* contain exactly one `blinded_payinfo` per
* `invoice_paths`.`blinded_path`. */
if (tal_count(b12->invoice_paths)
!= tal_count(b12->invoice_blindedpay)) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Wrong blinding info: %zu paths, %zu payinfo",
tal_count(b12->invoice_paths),
tal_count(b12->invoice_blindedpay));
}
/* FIXME: do MPP across these! We choose first one. */
p->blindedpath = tal_steal(p, b12->invoice_paths[0]);
p->blindedpay = tal_steal(p, b12->invoice_blindedpay[0]);

View File

@ -1002,7 +1002,7 @@ void topology_add_sync_waiter_(const tal_t *ctx UNNEEDED,
u8 *towire_announcement_signatures(const tal_t *ctx UNNEEDED, const struct channel_id *channel_id UNNEEDED, struct short_channel_id short_channel_id UNNEEDED, const secp256k1_ecdsa_signature *node_signature UNNEEDED, const secp256k1_ecdsa_signature *bitcoin_signature UNNEEDED)
{ fprintf(stderr, "towire_announcement_signatures called!\n"); abort(); }
/* Generated stub for towire_channel_reestablish */
u8 *towire_channel_reestablish(const tal_t *ctx UNNEEDED, const struct channel_id *channel_id UNNEEDED, u64 next_commitment_number UNNEEDED, u64 next_revocation_number UNNEEDED, const struct secret *your_last_per_commitment_secret UNNEEDED, const struct pubkey *my_current_per_commitment_point UNNEEDED, const struct tlv_channel_reestablish_tlvs *tlvs UNNEEDED)
u8 *towire_channel_reestablish(const tal_t *ctx UNNEEDED, const struct channel_id *channel_id UNNEEDED, u64 next_commitment_number UNNEEDED, u64 next_revocation_number UNNEEDED, const struct secret *your_last_per_commitment_secret UNNEEDED, const struct pubkey *my_current_per_commitment_point UNNEEDED, const struct tlv_channel_reestablish_tlvs *channel_reestablish UNNEEDED)
{ fprintf(stderr, "towire_channel_reestablish called!\n"); abort(); }
/* Generated stub for towire_channeld_dev_memleak */
u8 *towire_channeld_dev_memleak(const tal_t *ctx UNNEEDED)