Browse Source

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 <rusty@rustcorp.com.au>
fix-mocks
Rusty Russell 4 years ago
parent
commit
4d086e9939
  1. 2
      common/Makefile
  2. 299
      common/bolt12.c
  3. 92
      common/bolt12.h
  4. 217
      common/test/exp-run-bolt12_decode.c

2
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

299
common/bolt12.c

@ -0,0 +1,299 @@
#include <bitcoin/block.h>
#include <bitcoin/chainparams.h>
#include <ccan/cast/cast.h>
#include <ccan/crypto/sha256/sha256.h>
#include <ccan/mem/mem.h>
#include <common/bech32.h>
#include <common/bech32_util.h>
#include <common/bolt12.h>
#include <common/bolt12_merkle.h>
#include <common/features.h>
#include <secp256k1_schnorrsig.h>
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;
}

92
common/bolt12.h

@ -0,0 +1,92 @@
#ifndef LIGHTNING_COMMON_BOLT12_H
#define LIGHTNING_COMMON_BOLT12_H
#include "config.h"
#include <wire/bolt12_exp_wiregen.h>
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 */

217
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 <assert.h>
#include <ccan/array_size/array_size.h>
#include <ccan/tal/grab_file/grab_file.h>
#include <ccan/tal/path/path.h>
/* 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;
}
Loading…
Cancel
Save