core-lightning/devtools/bolt12-cli.c
Rusty Russell fea4eb8968 common: helper to generate bolt12 test vector bolt12/offers-test.json.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2023-09-21 13:31:03 +09:30

983 lines
30 KiB
C

#include "config.h"
#include <bitcoin/chainparams.h>
#include <ccan/array_size/array_size.h>
#include <ccan/err/err.h>
#include <ccan/opt/opt.h>
#include <ccan/tal/str/str.h>
#include <common/bech32_util.h>
#include <common/bolt12_merkle.h>
#include <common/configdir.h>
#include <common/features.h>
#include <common/iso4217.h>
#include <common/setup.h>
#include <common/type_to_string.h>
#include <common/version.h>
#include <inttypes.h>
#include <secp256k1_schnorrsig.h>
#include <stdio.h>
#include <time.h>
#define NO_ERROR 0
#define ERROR_BAD_DECODE 1
#define ERROR_USAGE 3
static bool well_formed = true;
/* Tal wrappers for opt. */
static void *opt_allocfn(size_t size)
{
return tal_arr_label(NULL, char, size, TAL_LABEL("opt_allocfn", ""));
}
static void *tal_reallocfn(void *ptr, size_t size)
{
if (!ptr)
return opt_allocfn(size);
tal_resize_(&ptr, 1, size, false);
return ptr;
}
static void tal_freefn(void *ptr)
{
tal_free(ptr);
}
static char *fmt_time(const tal_t *ctx, u64 time)
{
/* ctime is not sane. Take pointer, returns \n in string. */
time_t t = time;
const char *p = ctime(&t);
return tal_fmt(ctx, "%.*s", (int)strcspn(p, "\n"), p);
}
static bool must_str(bool expected, const char *complaint, const char *fieldname)
{
if (!expected) {
fprintf(stderr, "%s %s\n", complaint, fieldname);
well_formed = false;
return false;
}
return true;
}
#define must_have(obj, field) \
must_str((obj)->field != NULL, "Missing", stringify(field))
#define must_not_have(obj, field) \
must_str((obj)->field == NULL, "Unnecessary", stringify(field))
static void print_offer_chains(const struct bitcoin_blkid *chains)
{
printf("offer_chains:");
for (size_t i = 0; i < tal_count(chains); i++) {
printf(" %s", type_to_string(tmpctx, struct bitcoin_blkid, &chains[i]));
}
printf("\n");
}
static void print_hex(const char *fieldname, const u8 *bin)
{
printf("%s: %s\n", fieldname, tal_hex(tmpctx, bin));
}
static void print_invreq_chain(const struct bitcoin_blkid *chain)
{
printf("invreq_chain: %s\n",
type_to_string(tmpctx, struct bitcoin_blkid, chain));
}
static bool print_offer_amount(const struct bitcoin_blkid *chains,
const char *iso4217, u64 amount)
{
const char *currency;
unsigned int minor_unit;
bool ok = true;
/* BOLT-offers #12:
* - if a specific minimum `offer_amount` is required for successful payment:
* - MUST set `offer_amount` to the amount expected (per item).
* - if the currency for `offer_amount` is that of all entries in `chains`:
* - MUST specify `amount` in multiples of the minimum lightning-payable unit
* (e.g. milli-satoshis for bitcoin).
* - otherwise:
* - MUST specify `offer_currency` `iso4217` as an ISO 4712 three-letter code.
* - MUST specify `offer_amount` in the currency unit adjusted by the ISO 4712
* exponent (e.g. USD cents).
* - otherwise:
* - MUST NOT set `offer_amount`
* - MUST NOT set `offer_currency`
*/
if (!iso4217) {
if (tal_count(chains) == 0)
currency = "bc";
else {
const struct chainparams *ch;
ch = chainparams_by_chainhash(&chains[0]);
if (!ch) {
currency = tal_fmt(tmpctx, "UNKNOWN CHAINHASH %s",
type_to_string(tmpctx,
struct bitcoin_blkid,
&chains[0]));
ok = false;
} else
currency = ch->lightning_hrp;
}
minor_unit = 11;
} else {
const struct iso4217_name_and_divisor *iso;
iso = find_iso4217(iso4217, tal_bytelen(iso4217));
if (iso) {
minor_unit = iso->minor_unit;
currency = iso->name;
} else {
minor_unit = 0;
currency = tal_fmt(tmpctx, "%.*s (UNKNOWN CURRENCY)",
(int)tal_bytelen(iso4217), iso4217);
ok = false;
}
}
if (!minor_unit)
printf("offer_amount: %"PRIu64"%s\n", amount, currency);
else {
u64 minor_div = 1;
for (size_t i = 0; i < minor_unit; i++)
minor_div *= 10;
printf("offer_amount: %"PRIu64".%.*"PRIu64"%s\n",
amount / minor_div, minor_unit, amount % minor_div,
currency);
}
return ok;
}
static bool print_utf8(const char *fieldname, const char *description)
{
bool valid = utf8_check(description, tal_bytelen(description));
printf("%s: %.*s%s\n", fieldname,
(int)tal_bytelen(description), description,
valid ? "" : "(INVALID UTF-8)");
return valid;
}
static void print_node_id(const char *fieldname, const struct pubkey *node_id)
{
printf("%s: %s\n",
fieldname, type_to_string(tmpctx, struct pubkey, node_id));
}
static void print_u64(const char *fieldname, u64 max)
{
printf("%s: %"PRIu64"\n", fieldname, max);
}
static bool print_recurrance(const struct recurrence *recurrence,
const struct recurrence_paywindow *paywindow,
const u32 *limit,
const struct recurrence_base *base)
{
const char *unit;
bool ok = true;
/* BOLT-offers-recurrence #12:
* Thus, each offer containing a recurring payment has:
* 1. A `time_unit` defining 0 (seconds), 1 (days), 2 (months),
* 3 (years).
* 2. A `period`, defining how often (in `time_unit`) it has to be paid.
* 3. An optional `recurrence_limit` of total payments to be paid.
* 4. An optional `recurrence_base`:
* * `basetime`, defining when the first period starts
* in seconds since 1970-01-01 UTC.
* * `start_any_period` if non-zero, meaning you don't have to start
* paying at the period indicated by `basetime`, but can use
* `recurrence_start` to indicate what period you are starting at.
* 5. An optional `recurrence_paywindow`:
* * `seconds_before`, defining how many seconds prior to the start of
* the period a payment will be accepted.
* * `proportional_amount`, if set indicating that a payment made
* during the period itself will be charged proportionally to the
* remaining time in the period (e.g. 150 seconds into a 1500
* second period gives a 10% discount).
* * `seconds_after`, defining how many seconds after the start of the
* period a payment will be accepted.
* If this field is missing, payment will be accepted during the prior
* period and the paid-for period.
*/
switch (recurrence->time_unit) {
case 0:
unit = "seconds";
break;
case 1:
unit = "days";
break;
case 2:
unit = "months";
break;
case 3:
unit = "years";
break;
default:
fprintf(stderr, "recurrence: unknown time_unit %u", recurrence->time_unit);
unit = "";
ok = false;
}
printf("offer_recurrence: every %u %s", recurrence->period, unit);
if (limit)
printf(" limit %u", *limit);
if (base) {
printf(" start %"PRIu64" (%s)",
base->basetime,
fmt_time(tmpctx, base->basetime));
if (base->start_any_period)
printf(" (can start any period)");
}
if (paywindow) {
printf(" paywindow -%u to +%u",
paywindow->seconds_before, paywindow->seconds_after);
if (paywindow->proportional_amount)
printf(" (pay proportional)");
}
printf("\n");
return ok;
}
static void print_abstime(const char *fieldname, u64 expiry)
{
printf("%s: %"PRIu64" (%s)\n", fieldname,
expiry, fmt_time(tmpctx, expiry));
}
static void print_features(const char *fieldname, const u8 *features)
{
printf("%s:", fieldname);
for (size_t i = 0; i < tal_bytelen(features) * CHAR_BIT; i++) {
if (feature_is_set(features, i))
printf(" %zu", i);
}
printf("\n");
}
static bool print_blindedpaths(const char *fieldname,
struct blinded_path **paths,
struct blinded_payinfo **blindedpay)
{
size_t bp_idx = 0;
for (size_t i = 0; i < tal_count(paths); i++) {
struct onionmsg_hop **p = paths[i]->path;
printf("%s %zu/%zu: blinding %s",
fieldname,
i, tal_count(paths),
type_to_string(tmpctx, struct pubkey,
&paths[i]->blinding));
printf("%s %zu/%zu: path ",
fieldname,
i, tal_count(paths));
for (size_t j = 0; j < tal_count(p); j++) {
printf(" %s:%s",
type_to_string(tmpctx, struct pubkey,
&p[j]->blinded_node_id),
tal_hex(tmpctx, p[j]->encrypted_recipient_data));
if (blindedpay) {
if (bp_idx < tal_count(blindedpay))
printf("fee=%u/%u,cltv=%u,features=%s",
blindedpay[bp_idx]->fee_base_msat,
blindedpay[bp_idx]->fee_proportional_millionths,
blindedpay[bp_idx]->cltv_expiry_delta,
tal_hex(tmpctx,
blindedpay[bp_idx]->features));
bp_idx++;
}
}
printf("\n");
}
if (blindedpay && tal_count(blindedpay) != bp_idx) {
fprintf(stderr, "Expected %zu blindedpay fields, got %zu\n",
bp_idx, tal_count(blindedpay));
return false;
}
return true;
}
static bool print_signature(const char *messagename,
const char *fieldname,
const struct tlv_field *fields,
const struct pubkey *node_id,
const struct bip340sig *sig)
{
struct sha256 m, shash;
/* No key, it's already invalid */
if (!node_id)
return false;
merkle_tlv(fields, &m);
sighash_from_merkle(messagename, fieldname, &m, &shash);
if (!check_schnorr_sig(&shash, &node_id->pubkey, sig)) {
fprintf(stderr, "%s: INVALID\n", fieldname);
return false;
}
printf("%s: %s\n",
fieldname,
type_to_string(tmpctx, struct bip340sig, sig));
return true;
}
static void print_recurrence_counter(const u32 *recurrence_counter,
const u32 *recurrence_start)
{
printf("invreq_recurrence_counter: %u", *recurrence_counter);
if (recurrence_start)
printf(" (start +%u)", *recurrence_start);
printf("\n");
}
static bool print_recurrence_counter_with_base(const u32 *recurrence_counter,
const u32 *recurrence_start,
const u64 *recurrence_base)
{
if (!recurrence_base) {
fprintf(stderr, "Missing recurrence_base\n");
return false;
}
printf("invreq_recurrence_counter: %u", *recurrence_counter);
if (recurrence_start)
printf(" (start +%u)", *recurrence_start);
printf(" (base %"PRIu64")\n", *recurrence_base);
return true;
}
static void print_hash(const char *fieldname, const struct sha256 *hash)
{
printf("%s: %s\n",
fieldname, type_to_string(tmpctx, struct sha256, hash));
}
static void print_relative_expiry(u64 *created_at, u32 *relative)
{
/* Ignore if already malformed */
if (!created_at)
return;
/* 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 (!relative)
printf("invoice_relative_expiry: %u (%s) (default)\n",
BOLT12_DEFAULT_REL_EXPIRY,
fmt_time(tmpctx, *created_at + BOLT12_DEFAULT_REL_EXPIRY));
else
printf("invoice_relative_expiry: %u (%s)\n", *relative,
fmt_time(tmpctx, *created_at + *relative));
}
static void print_fallbacks(struct fallback_address **fallbacks)
{
for (size_t i = 0; i < tal_count(fallbacks); i++) {
/* FIXME: format properly! */
printf("invocice_fallbacks: %u %s\n",
fallbacks[i]->version,
tal_hex(tmpctx, fallbacks[i]->address));
}
}
static void print_msat(const char *fieldname, u64 amount)
{
printf("%s: %s\n", fieldname, fmt_amount_msat(tmpctx, amount_msat(amount)));
}
static bool print_extra_fields(const struct tlv_field *fields)
{
bool ok = true;
for (size_t i = 0; i < tal_count(fields); i++) {
if (fields[i].meta)
continue;
if (fields[i].numtype % 2) {
printf("UNKNOWN EVEN field %"PRIu64": %s\n",
fields[i].numtype,
tal_hexstr(tmpctx, fields[i].value, fields[i].length));
ok = false;
} else {
printf("Unknown field %"PRIu64": %s\n",
fields[i].numtype,
tal_hexstr(tmpctx, fields[i].value, fields[i].length));
}
}
return ok;
}
static u64 get_offer_type(const char *name)
{
u64 val;
char *endptr;
struct name_map {
const char *name;
u64 val;
} map[] = {
/* BOLT-offers #12:
* 1. `tlv_stream`: `offer`
* 2. types:
* 1. type: 2 (`offer_chains`)
* 2. data:
* * [`...*chain_hash`:`chains`]
* 1. type: 4 (`offer_metadata`)
* 2. data:
* * [`...*byte`:`data`]
* 1. type: 6 (`offer_currency`)
* 2. data:
* * [`...*utf8`:`iso4217`]
* 1. type: 8 (`offer_amount`)
* 2. data:
* * [`tu64`:`amount`]
* 1. type: 10 (`offer_description`)
* 2. data:
* * [`...*utf8`:`description`]
* 1. type: 12 (`offer_features`)
* 2. data:
* * [`...*byte`:`features`]
* 1. type: 14 (`offer_absolute_expiry`)
* 2. data:
* * [`tu64`:`seconds_from_epoch`]
* 1. type: 16 (`offer_paths`)
* 2. data:
* * [`...*blinded_path`:`paths`]
* 1. type: 18 (`offer_issuer`)
* 2. data:
* * [`...*utf8`:`issuer`]
* 1. type: 20 (`offer_quantity_max`)
* 2. data:
* * [`tu64`:`max`]
* 1. type: 22 (`offer_node_id`)
* 2. data:
* * [`point`:`node_id`]
*/
{ "offer_chains", 2 },
{ "offer_metadata", 4 },
{ "offer_currency", 6 },
{ "offer_amount", 8 },
{ "offer_description", 10 },
{ "offer_features", 12 },
{ "offer_absolute_expiry", 14 },
{ "offer_paths", 16 },
{ "offer_issuer", 18 },
{ "offer_quantity_max", 20 },
{ "offer_node_id", 22 },
/* BOLT-offers #12:
* 1. `tlv_stream`: `invoice_request`
* 2. types:
* 1. type: 0 (`invreq_metadata`)
* 2. data:
* * [`...*byte`:`blob`]
* 1. type: 2 (`offer_chains`)
* 2. data:
* * [`...*chain_hash`:`chains`]
* 1. type: 4 (`offer_metadata`)
* 2. data:
* * [`...*byte`:`data`]
* 1. type: 6 (`offer_currency`)
* 2. data:
* * [`...*utf8`:`iso4217`]
* 1. type: 8 (`offer_amount`)
* 2. data:
* * [`tu64`:`amount`]
* 1. type: 10 (`offer_description`)
* 2. data:
* * [`...*utf8`:`description`]
* 1. type: 12 (`offer_features`)
* 2. data:
* * [`...*byte`:`features`]
* 1. type: 14 (`offer_absolute_expiry`)
* 2. data:
* * [`tu64`:`seconds_from_epoch`]
* 1. type: 16 (`offer_paths`)
* 2. data:
* * [`...*blinded_path`:`paths`]
* 1. type: 18 (`offer_issuer`)
* 2. data:
* * [`...*utf8`:`issuer`]
* 1. type: 20 (`offer_quantity_max`)
* 2. data:
* * [`tu64`:`max`]
* 1. type: 22 (`offer_node_id`)
* 2. data:
* * [`point`:`node_id`]
* 1. type: 80 (`invreq_chain`)
* 2. data:
* * [`chain_hash`:`chain`]
* 1. type: 82 (`invreq_amount`)
* 2. data:
* * [`tu64`:`msat`]
* 1. type: 84 (`invreq_features`)
* 2. data:
* * [`...*byte`:`features`]
* 1. type: 86 (`invreq_quantity`)
* 2. data:
* * [`tu64`:`quantity`]
* 1. type: 88 (`invreq_payer_id`)
* 2. data:
* * [`point`:`key`]
* 1. type: 89 (`invreq_payer_note`)
* 2. data:
* * [`...*utf8`:`note`]
* 1. type: 240 (`signature`)
* 2. data:
* * [`bip340sig`:`sig`]
*/
{ "invreq_metadata", 0 },
{ "invreq_chain", 80 },
{ "invreq_amount", 82 },
{ "invreq_features", 84 },
{ "invreq_quantity", 86 },
{ "invreq_payer_id", 88 },
{ "invreq_payer_note", 89 },
{ "signature", 240 },
/* BOLT-offers #12:
* 1. `tlv_stream`: `invoice`
* 2. types:
* 1. type: 0 (`invreq_metadata`)
* 2. data:
* * [`...*byte`:`blob`]
* 1. type: 2 (`offer_chains`)
* 2. data:
* * [`...*chain_hash`:`chains`]
* 1. type: 4 (`offer_metadata`)
* 2. data:
* * [`...*byte`:`data`]
* 1. type: 6 (`offer_currency`)
* 2. data:
* * [`...*utf8`:`iso4217`]
* 1. type: 8 (`offer_amount`)
* 2. data:
* * [`tu64`:`amount`]
* 1. type: 10 (`offer_description`)
* 2. data:
* * [`...*utf8`:`description`]
* 1. type: 12 (`offer_features`)
* 2. data:
* * [`...*byte`:`features`]
* 1. type: 14 (`offer_absolute_expiry`)
* 2. data:
* * [`tu64`:`seconds_from_epoch`]
* 1. type: 16 (`offer_paths`)
* 2. data:
* * [`...*blinded_path`:`paths`]
* 1. type: 18 (`offer_issuer`)
* 2. data:
* * [`...*utf8`:`issuer`]
* 1. type: 20 (`offer_quantity_max`)
* 2. data:
* * [`tu64`:`max`]
* 1. type: 22 (`offer_node_id`)
* 2. data:
* * [`point`:`node_id`]
* 1. type: 80 (`invreq_chain`)
* 2. data:
* * [`chain_hash`:`chain`]
* 1. type: 82 (`invreq_amount`)
* 2. data:
* * [`tu64`:`msat`]
* 1. type: 84 (`invreq_features`)
* 2. data:
* * [`...*byte`:`features`]
* 1. type: 86 (`invreq_quantity`)
* 2. data:
* * [`tu64`:`quantity`]
* 1. type: 88 (`invreq_payer_id`)
* 2. data:
* * [`point`:`key`]
* 1. type: 89 (`invreq_payer_note`)
* 2. data:
* * [`...*utf8`:`note`]
* 1. type: 160 (`invoice_paths`)
* 2. data:
* * [`...*blinded_path`:`paths`]
* 1. type: 162 (`invoice_blindedpay`)
* 2. data:
* * [`...*blinded_payinfo`:`payinfo`]
* 1. type: 164 (`invoice_created_at`)
* 2. data:
* * [`tu64`:`timestamp`]
* 1. type: 166 (`invoice_relative_expiry`)
* 2. data:
* * [`tu32`:`seconds_from_creation`]
* 1. type: 168 (`invoice_payment_hash`)
* 2. data:
* * [`sha256`:`payment_hash`]
* 1. type: 170 (`invoice_amount`)
* 2. data:
* * [`tu64`:`msat`]
* 1. type: 172 (`invoice_fallbacks`)
* 2. data:
* * [`...*fallback_address`:`fallbacks`]
* 1. type: 174 (`invoice_features`)
* 2. data:
* * [`...*byte`:`features`]
* 1. type: 176 (`invoice_node_id`)
* 2. data:
* * [`point`:`node_id`]
* 1. type: 240 (`signature`)
* 2. data:
* * [`bip340sig`:`sig`]
*/
{ "invoice_paths", 160 },
{ "invoice_blindedpay", 162 },
{ "invoice_created_at", 164 },
{ "invoice_relative_expiry", 166 },
{ "invoice_payment_hash", 168 },
{ "invoice_amount", 170 },
{ "invoice_fallbacks", 172 },
{ "invoice_features", 174 },
{ "invoice_node_id", 176 },
};
for (size_t i = 0; i < ARRAY_SIZE(map); i++) {
if (streq(map[i].name, name))
return map[i].val;
}
/* Numeric value */
val = strtoul(name, &endptr, 0);
if (*endptr)
errx(1, "Unknown value %s", name);
return val;
}
static u8 *get_tlv_val(const tal_t *ctx, const char *val)
{
u8 *data = tal_hexdata(ctx, val, strlen(val));
if (data)
return data;
/* Literal string */
return tal_dup_arr(ctx, u8, (u8 *)val, strlen(val), 0);
}
int main(int argc, char *argv[])
{
const tal_t *ctx = tal(NULL, char);
const char *method;
char *hrp;
u8 *data;
char *fail;
bool to_hex = false;
common_setup(argv[0]);
opt_set_alloc(opt_allocfn, tal_reallocfn, tal_freefn);
opt_register_noarg("--help|-h", opt_usage_and_exit,
"decode|decodehex <bolt12>\n"
"encodehex <hrp> <hexstr>...\n"
"encode <hrp> [<tlvname> <tlvval>]...",
"Show this message");
opt_register_version();
opt_early_parse(argc, argv, opt_log_stderr_exit);
opt_parse(&argc, argv, opt_log_stderr_exit);
method = argv[1];
if (!method)
errx(ERROR_USAGE, "Need at least one argument\n%s",
opt_usage(argv[0], NULL));
if (streq(method, "encodehex")) {
char *nospaces;
if (argc < 4)
errx(ERROR_USAGE, "Need hrp and hexstr...\n%s",
opt_usage(argv[0], NULL));
nospaces = tal_arr(ctx, char, 0);
for (size_t i = 3; i < argc; i++) {
const char *src;
for (src = argv[i]; *src; src++) {
if (cisspace(*src))
continue;
tal_arr_expand(&nospaces, *src);
}
}
data = tal_hexdata(ctx, nospaces, tal_bytelen(nospaces));
if (!data)
errx(ERROR_USAGE, "Invalid hexstr\n%s",
opt_usage(argv[0], NULL));
printf("%s\n", to_bech32_charset(ctx, argv[2], data));
goto out;
}
if (streq(method, "encode")) {
data = tal_arr(ctx, u8, 0);
/* We encode literally, to make it possible to create invalid ones for
* testing! */
for (size_t i = 3; i < argc; i += 2) {
u64 tlvtype = get_offer_type(argv[i]);
u8 *tlvval = get_tlv_val(ctx, argv[i+1]);
towire_bigsize(&data, tlvtype);
towire_bigsize(&data, tal_bytelen(tlvval));
towire(&data, tlvval, tal_bytelen(tlvval));
}
printf("%s\n", to_bech32_charset(ctx, argv[2], data));
goto out;
}
if (streq(method, "decode"))
to_hex = false;
else if (streq(method, "decodehex"))
to_hex = true;
else
errx(ERROR_USAGE,
"Need encodehex/decode/decodehex argument\n%s",
opt_usage(argv[0], NULL));
if (!argv[2])
errx(ERROR_USAGE, "Need argument\n%s",
opt_usage(argv[0], NULL));
if (!from_bech32_charset(ctx, argv[2], strlen(argv[2]), &hrp, &data))
errx(ERROR_USAGE, "Bad bech32 string\n%s",
opt_usage(argv[0], NULL));
if (to_hex) {
const u8 *cursor = data;
size_t max = tal_bytelen(data);
printf("%s %s\n", hrp, tal_hex(ctx, data));
/* Now break down each element */
while (max) {
bigsize_t len;
const u8 *s = cursor;
fromwire_bigsize(&cursor, &max);
if (!cursor)
errx(ERROR_BAD_DECODE, "Bad type");
printf("%s ", tal_hexstr(ctx, s, cursor - s));
s = cursor;
len = fromwire_bigsize(&cursor, &max);
if (!cursor)
errx(ERROR_BAD_DECODE, "Bad len");
printf("%s ", tal_hexstr(ctx, s, cursor - s));
s = cursor;
fromwire(&cursor, &max, NULL, len);
if (!cursor)
errx(ERROR_BAD_DECODE, "Bad value");
printf("%s\n", tal_hexstr(ctx, s, cursor - s));
}
goto out;
}
if (streq(hrp, "lno")) {
struct sha256 offer_id;
const struct tlv_offer *offer
= offer_decode(ctx, argv[2], strlen(argv[2]),
NULL, NULL, &fail);
if (!offer)
errx(ERROR_BAD_DECODE, "Bad offer: %s", fail);
offer_offer_id(offer, &offer_id);
print_hash("offer_id", &offer_id);
if (offer->offer_chains)
print_offer_chains(offer->offer_chains);
if (offer->offer_amount)
well_formed &= print_offer_amount(offer->offer_chains,
offer->offer_currency,
*offer->offer_amount);
if (must_have(offer, offer_description))
well_formed &= print_utf8("offer_description", offer->offer_description);
if (offer->offer_features)
print_features("offer_features", offer->offer_features);
if (offer->offer_absolute_expiry)
print_abstime("offer_absolute_expiry", *offer->offer_absolute_expiry);
if (offer->offer_paths)
print_blindedpaths("offer_paths", offer->offer_paths, NULL);
if (offer->offer_issuer)
well_formed &= print_utf8("offer_issuer", offer->offer_issuer);
if (offer->offer_quantity_max)
print_u64("offer_quantity_max", *offer->offer_quantity_max);
if (must_have(offer, offer_node_id))
print_node_id("offer_node_id", offer->offer_node_id);
if (offer->offer_recurrence)
well_formed &= print_recurrance(offer->offer_recurrence,
offer->offer_recurrence_paywindow,
offer->offer_recurrence_limit,
offer->offer_recurrence_base);
if (!print_extra_fields(offer->fields))
well_formed = false;
} else if (streq(hrp, "lnr")) {
struct sha256 offer_id, invreq_id;
const struct tlv_invoice_request *invreq
= invrequest_decode(ctx, argv[2], strlen(argv[2]),
NULL, NULL, &fail);
if (!invreq)
errx(ERROR_BAD_DECODE, "Bad invreq: %s", fail);
if (invreq->offer_node_id) {
invreq_offer_id(invreq, &offer_id);
print_hash("offer_id", &offer_id);
}
invreq_invreq_id(invreq, &invreq_id);
print_hash("invreq_id", &invreq_id);
/* FIXME: We can do more intra-field checking! */
if (must_have(invreq, invreq_metadata))
print_hex("invreq_metadata", invreq->invreq_metadata);
if (invreq->offer_chains)
print_offer_chains(invreq->offer_chains);
if (invreq->offer_amount)
well_formed &= print_offer_amount(invreq->offer_chains,
invreq->offer_currency,
*invreq->offer_amount);
if (must_have(invreq, offer_description))
well_formed &= print_utf8("offer_description", invreq->offer_description);
if (invreq->offer_features)
print_features("offer_features", invreq->offer_features);
if (invreq->offer_absolute_expiry)
print_abstime("offer_absolute_expiry", *invreq->offer_absolute_expiry);
if (must_have(invreq, offer_paths))
print_blindedpaths("offer_paths", invreq->offer_paths, NULL);
if (invreq->offer_issuer)
well_formed &= print_utf8("offer_issuer", invreq->offer_issuer);
if (invreq->offer_quantity_max)
print_u64("offer_quantity_max", *invreq->offer_quantity_max);
if (invreq->offer_node_id)
print_node_id("offer_node_id", invreq->offer_node_id);
if (invreq->offer_recurrence)
well_formed &= print_recurrance(invreq->offer_recurrence,
invreq->offer_recurrence_paywindow,
invreq->offer_recurrence_limit,
invreq->offer_recurrence_base);
if (invreq->invreq_chain)
print_invreq_chain(invreq->invreq_chain);
if (invreq->invreq_amount)
print_msat("invreq_amount", *invreq->invreq_amount);
if (invreq->invreq_features)
print_features("invreq_features", invreq->invreq_features);
if (invreq->invreq_quantity)
print_u64("invreq_quantity", *invreq->invreq_quantity);
if (must_have(invreq, invreq_payer_id))
print_node_id("invreq_payer_id", invreq->invreq_payer_id);
if (invreq->invreq_payer_note)
well_formed &= print_utf8("invreq_payer_note", invreq->invreq_payer_note);
if (invreq->invreq_recurrence_counter) {
print_recurrence_counter(invreq->invreq_recurrence_counter,
invreq->invreq_recurrence_start);
} else {
must_not_have(invreq, invreq_recurrence_start);
}
if (must_have(invreq, signature)) {
well_formed = print_signature("invoice_request",
"signature",
invreq->fields,
invreq->invreq_payer_id,
invreq->signature);
}
if (!print_extra_fields(invreq->fields))
well_formed = false;
} else if (streq(hrp, "lni")) {
struct sha256 offer_id, invreq_id;
const struct tlv_invoice *invoice
= invoice_decode(ctx, argv[2], strlen(argv[2]),
NULL, NULL, &fail);
if (!invoice)
errx(ERROR_BAD_DECODE, "Bad invoice: %s", fail);
if (invoice->invreq_payer_id) {
if (invoice->offer_node_id) {
invoice_offer_id(invoice, &offer_id);
print_hash("offer_id", &offer_id);
}
invoice_invreq_id(invoice, &invreq_id);
print_hash("invreq_id", &invreq_id);
}
/* FIXME: We can do more intra-field checking! */
if (must_have(invoice, invreq_metadata))
print_hex("invreq_metadata", invoice->invreq_metadata);
if (invoice->offer_chains)
print_offer_chains(invoice->offer_chains);
if (invoice->offer_amount)
well_formed &= print_offer_amount(invoice->offer_chains,
invoice->offer_currency,
*invoice->offer_amount);
if (must_have(invoice, offer_description))
well_formed &= print_utf8("offer_description", invoice->offer_description);
if (invoice->offer_features)
print_features("offer_features", invoice->offer_features);
if (invoice->offer_absolute_expiry)
print_abstime("offer_absolute_expiry", *invoice->offer_absolute_expiry);
if (must_have(invoice, offer_paths))
print_blindedpaths("offer_paths", invoice->offer_paths, NULL);
if (invoice->offer_issuer)
well_formed &= print_utf8("offer_issuer", invoice->offer_issuer);
if (invoice->offer_quantity_max)
print_u64("offer_quantity_max", *invoice->offer_quantity_max);
if (invoice->offer_node_id)
print_node_id("offer_node_id", invoice->offer_node_id);
if (invoice->offer_recurrence)
well_formed &= print_recurrance(invoice->offer_recurrence,
invoice->offer_recurrence_paywindow,
invoice->offer_recurrence_limit,
invoice->offer_recurrence_base);
if (invoice->invreq_chain)
print_invreq_chain(invoice->invreq_chain);
if (invoice->invreq_amount)
print_msat("invreq_amount", *invoice->invreq_amount);
if (invoice->invreq_features)
print_features("invreq_features", invoice->invreq_features);
if (invoice->invreq_quantity)
print_u64("invreq_quantity", *invoice->invreq_quantity);
if (must_have(invoice, invreq_payer_id))
print_node_id("invreq_payer_id", invoice->invreq_payer_id);
if (invoice->invreq_payer_note)
well_formed &= print_utf8("invreq_payer_note", invoice->invreq_payer_note);
if (invoice->invreq_recurrence_counter) {
well_formed &= print_recurrence_counter_with_base(invoice->invreq_recurrence_counter,
invoice->invreq_recurrence_start,
invoice->invoice_recurrence_basetime);
} else {
must_not_have(invoice, invreq_recurrence_start);
}
if (must_have(invoice, invoice_paths))
print_blindedpaths("invoice_paths",
invoice->invoice_paths,
invoice->invoice_blindedpay);
if (must_have(invoice, invoice_created_at))
print_abstime("invoice_created_at",
*invoice->invoice_created_at);
print_relative_expiry(invoice->invoice_created_at,
invoice->invoice_relative_expiry);
if (must_have(invoice, invoice_payment_hash))
print_hash("invoice_payment_hash", invoice->invoice_payment_hash);
if (must_have(invoice, invoice_amount))
print_msat("invoice_amount", *invoice->invoice_amount);
if (invoice->invoice_fallbacks)
print_fallbacks(invoice->invoice_fallbacks);
if (invoice->invoice_features)
print_features("invoice_features", invoice->invoice_features);
if (must_have(invoice, invoice_node_id))
print_node_id("invoice_node_id", invoice->invoice_node_id);
if (must_have(invoice, signature))
well_formed &= print_signature("invoice", "signature",
invoice->fields,
invoice->invoice_node_id,
invoice->signature);
if (!print_extra_fields(invoice->fields))
well_formed = false;
} else
errx(ERROR_BAD_DECODE, "Unknown prefix %s", hrp);
out:
tal_free(ctx);
common_shutdown();
if (well_formed)
return NO_ERROR;
else
return ERROR_BAD_DECODE;
}