mirror of
https://github.com/ElementsProject/lightning.git
synced 2024-11-19 01:43:36 +01:00
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:
parent
1b8a64f5e6
commit
1362448352
121
common/bolt12.c
121
common/bolt12.c
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
|
@ -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(); }
|
||||
|
@ -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",
|
||||
|
@ -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]);
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user