From 4d086e99397d83a0cfe94a39e3a4b27713c3bc55 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 9 Dec 2020 14:26:16 +1030 Subject: [PATCH] common/bolt12: encode/decode for bolt12 offer, invoice_request and invoice Note the collapse of '+\s*' and the test cases from the spec. Signed-off-by: Rusty Russell --- common/Makefile | 2 + common/bolt12.c | 299 ++++++++++++++++++++++++++++ common/bolt12.h | 92 +++++++++ common/test/exp-run-bolt12_decode.c | 217 ++++++++++++++++++++ 4 files changed, 610 insertions(+) create mode 100644 common/bolt12.c create mode 100644 common/bolt12.h create mode 100644 common/test/exp-run-bolt12_decode.c diff --git a/common/Makefile b/common/Makefile index c449fbfed..8cf98829e 100644 --- a/common/Makefile +++ b/common/Makefile @@ -9,6 +9,7 @@ COMMON_SRC_NOGEN := \ common/bip32.c \ common/blinding.c \ common/bolt11.c \ + common/bolt12.c \ common/channel_config.c \ common/channel_id.c \ common/coin_mvt.c \ @@ -87,6 +88,7 @@ endif COMMON_SRC_GEN := common/status_wiregen.c common/peer_status_wiregen.c ifeq ($(EXPERIMENTAL_FEATURES),1) +COMMON_SRC_NOGEN += common/bolt12.c COMMON_SRC_NOGEN += common/bolt12_merkle.c endif diff --git a/common/bolt12.c b/common/bolt12.c new file mode 100644 index 000000000..72c0e42f6 --- /dev/null +++ b/common/bolt12.c @@ -0,0 +1,299 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool bolt12_chains_match(const struct bitcoin_blkid *chains, + const struct chainparams *must_be_chain) +{ + size_t num_chains; + + /* BOLT-offers #12: + * - if the chain for the invoice is not solely bitcoin: + * - MUST specify `chains` the offer is valid for. + * - otherwise: + * - the bitcoin chain is implied as the first and only entry. + */ + /* BOLT-offers #12: + * The reader of an invoice_request: + *... + * - MUST fail the request if `chains` does not include (or + * imply) a supported chain. + */ + /* BOLT-offers #12: + * + * - if the chain for the invoice is not solely bitcoin: + * - MUST specify `chains` the invoice is valid for. + * - otherwise: + * - the bitcoin chain is implied as the first and only entry. + */ + num_chains = tal_count(chains); + if (num_chains == 0) { + num_chains = 1; + chains = &chainparams_for_network("bitcoin")->genesis_blockhash; + } + + for (size_t i = 0; i < num_chains; i++) { + if (bitcoin_blkid_eq(&chains[i], + &must_be_chain->genesis_blockhash)) + return true; + } + + return false; +} + +static char *check_features_and_chain(const tal_t *ctx, + const struct feature_set *our_features, + const struct chainparams *must_be_chain, + const u8 *features, + const struct bitcoin_blkid *chains) +{ + if (must_be_chain) { + if (!bolt12_chains_match(chains, must_be_chain)) + return tal_fmt(ctx, "wrong chain"); + } + + if (our_features) { + int badf = features_unsupported(our_features, features, + BOLT11_FEATURE); + if (badf != -1) + return tal_fmt(ctx, "unknown feature bit %i", badf); + } + + return NULL; +} + +static char *check_signature(const tal_t *ctx, + const struct tlv_field *fields, + const char *messagename, + const char *fieldname, + const struct pubkey32 *node_id, + const struct bip340sig *sig) +{ + struct sha256 m, shash; + + if (!node_id) + return tal_fmt(ctx, "Missing node_id"); + if (!sig) + return tal_fmt(ctx, "Missing signature"); + + merkle_tlv(fields, &m); + sighash_from_merkle(messagename, fieldname, &m, &shash); + if (secp256k1_schnorrsig_verify(secp256k1_ctx, + sig->u8, + shash.u.u8, + &node_id->pubkey) != 1) + return tal_fmt(ctx, "Invalid signature"); + return NULL; +} + +static const u8 *string_to_data(const tal_t *ctx, + const char *str, + size_t str_len, + const char *hrp_expected, + size_t *dlen, + char **fail) +{ + char *hrp; + u8 *data; + char *bech32; + size_t bech32_len; + bool have_plus = false; + + /* First we collapse +\s*, except at start/end. */ + bech32 = tal_arr(tmpctx, char, str_len); + bech32_len = 0; + for (size_t i = 0; i < str_len; i++) { + if (i != 0 && i+1 != str_len && !have_plus && str[i] == '+') { + have_plus = true; + continue; + } + if (have_plus && cisspace(str[i])) + continue; + have_plus = false; + bech32[bech32_len++] = str[i]; + } + + if (have_plus) { + *fail = tal_fmt(ctx, "unfinished string"); + return NULL; + } + + if (!from_bech32_charset(ctx, bech32, bech32_len, &hrp, &data)) { + *fail = tal_fmt(ctx, "invalid bech32 string"); + return NULL; + } + if (!streq(hrp, hrp_expected)) { + *fail = tal_fmt(ctx, "unexpected prefix %s", hrp); + data = tal_free(data); + } else + *dlen = tal_bytelen(data); + + tal_free(hrp); + return data; +} + +char *offer_encode(const tal_t *ctx, const struct tlv_offer *offer_tlv) +{ + u8 *wire; + + wire = tal_arr(tmpctx, u8, 0); + towire_offer(&wire, offer_tlv); + + return to_bech32_charset(ctx, "lno", wire); +} + +struct tlv_offer *offer_decode(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_offer *offer; + + offer = offer_decode_nosig(ctx, b12, b12len, + our_features, must_be_chain, fail); + + if (offer) { + *fail = check_signature(ctx, offer->fields, + "offer", "signature", + offer->node_id, offer->signature); + if (*fail) + offer = tal_free(offer); + } + return offer; +} + +struct tlv_offer *offer_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_offer *offer = tlv_offer_new(ctx); + const u8 *data; + size_t dlen; + + data = string_to_data(tmpctx, b12, b12len, "lno", &dlen, fail); + if (!data) + return tal_free(offer); + + if (!fromwire_offer(&data, &dlen, offer)) { + *fail = tal_fmt(ctx, "invalid offer data"); + return tal_free(offer); + } + + *fail = check_features_and_chain(ctx, + our_features, must_be_chain, + offer->features, + offer->chains); + if (*fail) + return tal_free(offer); + + return offer; +} + +char *invrequest_encode(const tal_t *ctx, const struct tlv_invoice_request *invrequest_tlv) +{ + u8 *wire; + + wire = tal_arr(tmpctx, u8, 0); + towire_invoice_request(&wire, invrequest_tlv); + + return to_bech32_charset(ctx, "lnr", wire); +} + +struct tlv_invoice_request *invrequest_decode(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_request *invrequest = tlv_invoice_request_new(ctx); + const u8 *data; + size_t dlen; + + data = string_to_data(tmpctx, b12, b12len, "lnr", &dlen, fail); + if (!data) + return tal_free(invrequest); + + if (!fromwire_invoice_request(&data, &dlen, invrequest)) { + *fail = tal_fmt(ctx, "invalid invoice_request data"); + return tal_free(invrequest); + } + + *fail = check_features_and_chain(ctx, + our_features, must_be_chain, + invrequest->features, + invrequest->chains); + if (*fail) + return tal_free(invrequest); + + return invrequest; +} + +char *invoice_encode(const tal_t *ctx, const struct tlv_invoice *invoice_tlv) +{ + u8 *wire; + + wire = tal_arr(tmpctx, u8, 0); + towire_invoice(&wire, 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 = tlv_invoice_new(ctx); + const u8 *data; + size_t dlen; + + data = string_to_data(tmpctx, b12, b12len, "lni", &dlen, fail); + if (!data) + return tal_free(invoice); + + if (!fromwire_invoice(&data, &dlen, invoice)) { + *fail = tal_fmt(ctx, "invalid invoice data"); + return tal_free(invoice); + } + + *fail = check_features_and_chain(ctx, + our_features, must_be_chain, + invoice->features, + invoice->chains); + if (*fail) + return tal_free(invoice); + + return invoice; +} + +struct tlv_invoice *invoice_decode(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; + + invoice = invoice_decode_nosig(ctx, b12, b12len, our_features, + must_be_chain, fail); + if (invoice) { + *fail = check_signature(ctx, invoice->fields, + "invoice", "signature", + invoice->node_id, invoice->signature); + if (*fail) + invoice = tal_free(invoice); + } + return invoice; +} diff --git a/common/bolt12.h b/common/bolt12.h new file mode 100644 index 000000000..47dd95125 --- /dev/null +++ b/common/bolt12.h @@ -0,0 +1,92 @@ +#ifndef LIGHTNING_COMMON_BOLT12_H +#define LIGHTNING_COMMON_BOLT12_H +#include "config.h" +#include + +struct feature_set; + +/** + * offer_encode - encode this complete bolt12 offer TLV into text. + */ +char *offer_encode(const tal_t *ctx, const struct tlv_offer *bolt12_tlv); + +/** + * offer_decode - decode this complete bolt12 text into a TLV. + * @ctx: the context to allocate return or *@fail off. + * @b12: the offer string + * @b12len: the offer string length + * @our_features: if non-NULL, feature set to check against. + * @must_be_chain: if non-NULL, chain to enforce. + * @fail: pointer to descriptive error string, set if this returns NULL. + * + * Note: checks signature! + */ +struct tlv_offer *offer_decode(const tal_t *ctx, const char *b12, size_t b12len, + const struct feature_set *our_features, + const struct chainparams *must_be_chain, + char **fail); + +/* Variant which does not check signature */ +struct tlv_offer *offer_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); + +/** + * invrequest_encode - encode this complete bolt12 invreq TLV into text. + */ +char *invrequest_encode(const tal_t *ctx, + const struct tlv_invoice_request *bolt12_tlv); + +/** + * invrequest_decode - decode this complete bolt12 text into a TLV. + * @ctx: the context to allocate return or *@fail off. + * @b12: the invoice_request string + * @b12len: the invoice_request string length + * @our_features: if non-NULL, feature set to check against. + * @must_be_chain: if non-NULL, chain to enforce. + * @fail: pointer to descriptive error string, set if this returns NULL. + * + * Note: invoice_request doesn't always have a signature, so no checking is done! + */ +struct tlv_invoice_request *invrequest_decode(const tal_t *ctx, + const char *b12, size_t b12len, + const struct feature_set *our_features, + const struct chainparams *must_be_chain, + char **fail); + +/** + * invoice_encode - encode this complete bolt12 invoice TLV into text. + */ +char *invoice_encode(const tal_t *ctx, const struct tlv_invoice *bolt12_tlv); + +/** + * invoice_decode - decode this complete bolt12 text into a TLV. + * @ctx: the context to allocate return or *@fail off. + * @b12: the invoice string + * @b12len: the invoice string length + * @our_features: if non-NULL, feature set to check against. + * @must_be_chain: if non-NULL, chain to enforce. + * @fail: pointer to descriptive error string, set if this returns NULL. + * + * Note: checks signature! + */ +struct tlv_invoice *invoice_decode(const tal_t *ctx, + const char *b12, size_t b12len, + const struct feature_set *our_features, + 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); + +/* Given a tal_arr of chains, does it contain this chain? */ +bool bolt12_chains_match(const struct bitcoin_blkid *chains, + const struct chainparams *must_be_chain); + +#endif /* LIGHTNING_COMMON_BOLT12_H */ diff --git a/common/test/exp-run-bolt12_decode.c b/common/test/exp-run-bolt12_decode.c new file mode 100644 index 000000000..07dfb13d2 --- /dev/null +++ b/common/test/exp-run-bolt12_decode.c @@ -0,0 +1,217 @@ +#include "config.h" +#include "../bolt12.c" +#include "../bech32_util.c" +#include "../bech32.c" +#include "../json.c" +#include +#include +#include +#include + +/* AUTOGENERATED MOCKS START */ +/* Generated stub for amount_asset_is_main */ +bool amount_asset_is_main(struct amount_asset *asset UNNEEDED) +{ fprintf(stderr, "amount_asset_is_main called!\n"); abort(); } +/* Generated stub for amount_asset_to_sat */ +struct amount_sat amount_asset_to_sat(struct amount_asset *asset UNNEEDED) +{ fprintf(stderr, "amount_asset_to_sat called!\n"); abort(); } +/* Generated stub for amount_sat */ +struct amount_sat amount_sat(u64 satoshis UNNEEDED) +{ fprintf(stderr, "amount_sat called!\n"); abort(); } +/* Generated stub for amount_sat_add */ + bool amount_sat_add(struct amount_sat *val UNNEEDED, + struct amount_sat a UNNEEDED, + struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_add called!\n"); abort(); } +/* Generated stub for amount_sat_eq */ +bool amount_sat_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_eq called!\n"); abort(); } +/* Generated stub for amount_sat_greater_eq */ +bool amount_sat_greater_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_greater_eq called!\n"); abort(); } +/* Generated stub for amount_sat_sub */ + bool amount_sat_sub(struct amount_sat *val UNNEEDED, + struct amount_sat a UNNEEDED, + struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_sub called!\n"); abort(); } +/* Generated stub for amount_sat_to_asset */ +struct amount_asset amount_sat_to_asset(struct amount_sat *sat UNNEEDED, const u8 *asset UNNEEDED) +{ fprintf(stderr, "amount_sat_to_asset called!\n"); abort(); } +/* Generated stub for amount_tx_fee */ +struct amount_sat amount_tx_fee(u32 fee_per_kw UNNEEDED, size_t weight UNNEEDED) +{ fprintf(stderr, "amount_tx_fee called!\n"); abort(); } +/* Generated stub for features_unsupported */ +int features_unsupported(const struct feature_set *our_features UNNEEDED, + const u8 *their_features UNNEEDED, + enum feature_place p UNNEEDED) +{ fprintf(stderr, "features_unsupported called!\n"); abort(); } +/* Generated stub for fromwire */ +const u8 *fromwire(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, void *copy UNNEEDED, size_t n UNNEEDED) +{ fprintf(stderr, "fromwire called!\n"); abort(); } +/* Generated stub for fromwire_amount_sat */ +struct amount_sat fromwire_amount_sat(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_amount_sat called!\n"); abort(); } +/* Generated stub for fromwire_bool */ +bool fromwire_bool(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_bool called!\n"); abort(); } +/* Generated stub for fromwire_fail */ +void *fromwire_fail(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_fail called!\n"); abort(); } +/* Generated stub for fromwire_invoice */ +bool fromwire_invoice(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, + struct tlv_invoice * record UNNEEDED) +{ fprintf(stderr, "fromwire_invoice called!\n"); abort(); } +/* Generated stub for fromwire_invoice_request */ +bool fromwire_invoice_request(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, + struct tlv_invoice_request * record UNNEEDED) +{ fprintf(stderr, "fromwire_invoice_request called!\n"); abort(); } +/* Generated stub for fromwire_offer */ +bool fromwire_offer(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, + struct tlv_offer * record UNNEEDED) +{ fprintf(stderr, "fromwire_offer called!\n"); abort(); } +/* Generated stub for fromwire_secp256k1_ecdsa_signature */ +void fromwire_secp256k1_ecdsa_signature(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, + secp256k1_ecdsa_signature *signature UNNEEDED) +{ fprintf(stderr, "fromwire_secp256k1_ecdsa_signature called!\n"); abort(); } +/* Generated stub for fromwire_sha256 */ +void fromwire_sha256(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct sha256 *sha256 UNNEEDED) +{ fprintf(stderr, "fromwire_sha256 called!\n"); abort(); } +/* Generated stub for fromwire_tal_arrn */ +u8 *fromwire_tal_arrn(const tal_t *ctx UNNEEDED, + const u8 **cursor UNNEEDED, size_t *max UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "fromwire_tal_arrn called!\n"); abort(); } +/* Generated stub for fromwire_u16 */ +u16 fromwire_u16(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u16 called!\n"); abort(); } +/* Generated stub for fromwire_u32 */ +u32 fromwire_u32(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u32 called!\n"); abort(); } +/* Generated stub for fromwire_u64 */ +u64 fromwire_u64(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u64 called!\n"); abort(); } +/* Generated stub for fromwire_u8 */ +u8 fromwire_u8(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u8 called!\n"); abort(); } +/* Generated stub for fromwire_u8_array */ +void fromwire_u8_array(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, u8 *arr UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "fromwire_u8_array called!\n"); abort(); } +/* Generated stub for json_add_member */ +void json_add_member(struct json_stream *js UNNEEDED, + const char *fieldname UNNEEDED, + bool quote UNNEEDED, + const char *fmt UNNEEDED, ...) +{ fprintf(stderr, "json_add_member called!\n"); abort(); } +/* Generated stub for json_member_direct */ +char *json_member_direct(struct json_stream *js UNNEEDED, + const char *fieldname UNNEEDED, size_t extra UNNEEDED) +{ fprintf(stderr, "json_member_direct called!\n"); abort(); } +/* Generated stub for merkle_tlv */ +void merkle_tlv(const struct tlv_field *fields UNNEEDED, struct sha256 *merkle UNNEEDED) +{ fprintf(stderr, "merkle_tlv called!\n"); abort(); } +/* Generated stub for sighash_from_merkle */ +void sighash_from_merkle(const char *messagename UNNEEDED, + const char *fieldname UNNEEDED, + const struct sha256 *merkle UNNEEDED, + struct sha256 *sighash UNNEEDED) +{ fprintf(stderr, "sighash_from_merkle called!\n"); abort(); } +/* Generated stub for tlv_invoice_new */ +struct tlv_invoice *tlv_invoice_new(const tal_t *ctx UNNEEDED) +{ fprintf(stderr, "tlv_invoice_new called!\n"); abort(); } +/* Generated stub for tlv_invoice_request_new */ +struct tlv_invoice_request *tlv_invoice_request_new(const tal_t *ctx UNNEEDED) +{ fprintf(stderr, "tlv_invoice_request_new called!\n"); abort(); } +/* Generated stub for tlv_offer_new */ +struct tlv_offer *tlv_offer_new(const tal_t *ctx UNNEEDED) +{ fprintf(stderr, "tlv_offer_new called!\n"); abort(); } +/* Generated stub for towire */ +void towire(u8 **pptr UNNEEDED, const void *data UNNEEDED, size_t len UNNEEDED) +{ fprintf(stderr, "towire called!\n"); abort(); } +/* Generated stub for towire_amount_sat */ +void towire_amount_sat(u8 **pptr UNNEEDED, const struct amount_sat sat UNNEEDED) +{ fprintf(stderr, "towire_amount_sat called!\n"); abort(); } +/* Generated stub for towire_bool */ +void towire_bool(u8 **pptr UNNEEDED, bool v UNNEEDED) +{ fprintf(stderr, "towire_bool called!\n"); abort(); } +/* Generated stub for towire_invoice */ +void towire_invoice(u8 **pptr UNNEEDED, const struct tlv_invoice *record UNNEEDED) +{ fprintf(stderr, "towire_invoice called!\n"); abort(); } +/* Generated stub for towire_invoice_request */ +void towire_invoice_request(u8 **pptr UNNEEDED, const struct tlv_invoice_request *record UNNEEDED) +{ fprintf(stderr, "towire_invoice_request called!\n"); abort(); } +/* Generated stub for towire_offer */ +void towire_offer(u8 **pptr UNNEEDED, const struct tlv_offer *record UNNEEDED) +{ fprintf(stderr, "towire_offer called!\n"); abort(); } +/* Generated stub for towire_secp256k1_ecdsa_signature */ +void towire_secp256k1_ecdsa_signature(u8 **pptr UNNEEDED, + const secp256k1_ecdsa_signature *signature UNNEEDED) +{ fprintf(stderr, "towire_secp256k1_ecdsa_signature called!\n"); abort(); } +/* Generated stub for towire_sha256 */ +void towire_sha256(u8 **pptr UNNEEDED, const struct sha256 *sha256 UNNEEDED) +{ fprintf(stderr, "towire_sha256 called!\n"); abort(); } +/* Generated stub for towire_u16 */ +void towire_u16(u8 **pptr UNNEEDED, u16 v UNNEEDED) +{ fprintf(stderr, "towire_u16 called!\n"); abort(); } +/* Generated stub for towire_u32 */ +void towire_u32(u8 **pptr UNNEEDED, u32 v UNNEEDED) +{ fprintf(stderr, "towire_u32 called!\n"); abort(); } +/* Generated stub for towire_u64 */ +void towire_u64(u8 **pptr UNNEEDED, u64 v UNNEEDED) +{ fprintf(stderr, "towire_u64 called!\n"); abort(); } +/* Generated stub for towire_u8 */ +void towire_u8(u8 **pptr UNNEEDED, u8 v UNNEEDED) +{ fprintf(stderr, "towire_u8 called!\n"); abort(); } +/* Generated stub for towire_u8_array */ +void towire_u8_array(u8 **pptr UNNEEDED, const u8 *arr UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "towire_u8_array called!\n"); abort(); } +/* AUTOGENERATED MOCKS END */ + +int main(int argc, char *argv[]) +{ + char *json; + size_t i; + jsmn_parser parser; + jsmntok_t toks[5000]; + const jsmntok_t *t; + + setup_locale(); + setup_tmpctx(); + + if (argv[1]) + json = grab_file(tmpctx, argv[1]); + else { + char *dir = getenv("BOLTDIR"); + json = grab_file(tmpctx, + path_join(tmpctx, + dir ? dir : "../lightning-rfc", + "bolt12/format-string-test.json")); + if (!json) { + printf("test file not found, skipping\n"); + tal_free(tmpctx); + exit(0); + } + } + + jsmn_init(&parser); + if (jsmn_parse(&parser, json, strlen(json), toks, ARRAY_SIZE(toks)) < 0) + abort(); + + json_for_each_arr(i, t, toks) { + bool valid, actual; + const jsmntok_t *strtok; + char *fail; + const char *str; + size_t dlen; + struct json_escape *esc; + + json_to_bool(json, json_get_member(json, t, "valid"), &valid); + strtok = json_get_member(json, t, "string"); + esc = json_escape_string_(tmpctx, json + strtok->start, + strtok->end - strtok->start); + str = json_escape_unescape(tmpctx, esc); + actual = (string_to_data(tmpctx, str, strlen(str), + "lni", &dlen, &fail) != NULL); + assert(actual == valid); + } + tal_free(tmpctx); + return 0; +}