cln-plugin 0.7.0

A CLN plugin library. Write your plugin in Rust.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
#include "config.h"
#include <bitcoin/preimage.h>
#include <ccan/array_size/array_size.h>
#include <ccan/crypto/sha256/sha256.h>
#include <ccan/mem/mem.h>
#include <ccan/str/hex/hex.h>
#include <ccan/tal/str/str.h>
#include <common/bolt11.h>
#include <common/bolt12.h>
#include <common/bolt12_proof.h>
#include <common/json_param.h>
#include <common/json_parse_simple.h>
#include <common/json_stream.h>
#include <common/jsonrpc_errors.h>
#include <common/utils.h>
#include <plugins/libplugin.h>
#include <plugins/offers.h>
#include <plugins/offers_proof.h>

/* Default field types to include (in addition to mandatory ones). */
static const bigsize_t default_extras[] = {
	TLV_INVOICE_OFFER_DESCRIPTION,
	TLV_INVOICE_OFFER_ISSUER_ID,
	TLV_INVOICE_INVOICE_CREATED_AT,
	TLV_INVOICE_INVOICE_AMOUNT,
};

/* Mandatory field types (always included). */
static const bigsize_t mandatory_types[] = {
	TLV_INVOICE_INVREQ_PAYER_ID,
	TLV_INVOICE_INVOICE_PAYMENT_HASH,
	TLV_INVOICE_INVOICE_FEATURES,
	TLV_INVOICE_INVOICE_NODE_ID,
};

/* Name-to-number mapping for all known bolt12 invoice fields. */
static const struct {
	const char *name;
	bigsize_t num;
} field_names[] = {
	{ "offer_chains",            TLV_INVOICE_OFFER_CHAINS },
	{ "offer_metadata",          TLV_INVOICE_OFFER_METADATA },
	{ "offer_currency",          TLV_INVOICE_OFFER_CURRENCY },
	{ "offer_amount",            TLV_INVOICE_OFFER_AMOUNT },
	{ "offer_description",       TLV_INVOICE_OFFER_DESCRIPTION },
	{ "offer_features",          TLV_INVOICE_OFFER_FEATURES },
	{ "offer_absolute_expiry",   TLV_INVOICE_OFFER_ABSOLUTE_EXPIRY },
	{ "offer_paths",             TLV_INVOICE_OFFER_PATHS },
	{ "offer_issuer",            TLV_INVOICE_OFFER_ISSUER },
	{ "offer_quantity_max",      TLV_INVOICE_OFFER_QUANTITY_MAX },
	{ "offer_issuer_id",         TLV_INVOICE_OFFER_ISSUER_ID },
	{ "invreq_chain",            TLV_INVOICE_INVREQ_CHAIN },
	{ "invreq_amount",           TLV_INVOICE_INVREQ_AMOUNT },
	{ "invreq_features",         TLV_INVOICE_INVREQ_FEATURES },
	{ "invreq_quantity",         TLV_INVOICE_INVREQ_QUANTITY },
	{ "invreq_payer_id",         TLV_INVOICE_INVREQ_PAYER_ID },
	{ "invreq_payer_note",       TLV_INVOICE_INVREQ_PAYER_NOTE },
	{ "invreq_paths",            TLV_INVOICE_INVREQ_PATHS },
	{ "invreq_bip_353_name",     TLV_INVOICE_INVREQ_BIP_353_NAME },
	{ "invoice_paths",           TLV_INVOICE_INVOICE_PATHS },
	{ "invoice_blindedpay",      TLV_INVOICE_INVOICE_BLINDEDPAY },
	{ "invoice_created_at",      TLV_INVOICE_INVOICE_CREATED_AT },
	{ "invoice_relative_expiry", TLV_INVOICE_INVOICE_RELATIVE_EXPIRY },
	{ "invoice_payment_hash",    TLV_INVOICE_INVOICE_PAYMENT_HASH },
	{ "invoice_amount",          TLV_INVOICE_INVOICE_AMOUNT },
	{ "invoice_fallbacks",       TLV_INVOICE_INVOICE_FALLBACKS },
	{ "invoice_features",        TLV_INVOICE_INVOICE_FEATURES },
	{ "invoice_node_id",         TLV_INVOICE_INVOICE_NODE_ID },
	{ "signature",               TLV_INVOICE_SIGNATURE },
};

/* Returns name for a TLV type, or NULL if unknown. */
static const char *tlv_type_name(bigsize_t num)
{
	for (size_t i = 0; i < ARRAY_SIZE(field_names); i++) {
		if (field_names[i].num == num)
			return field_names[i].name;
	}
	return NULL;
}

/* Returns TLV type number for a field name, or UINT64_MAX if unknown. */
static bigsize_t tlv_name_to_num(const char *name)
{
	for (size_t i = 0; i < ARRAY_SIZE(field_names); i++) {
		if (streq(field_names[i].name, name))
			return field_names[i].num;
	}
	return UINT64_MAX;
}

/* Which "layer" does this TLV type belong to? */
enum field_layer { LAYER_OFFER, LAYER_INVREQ, LAYER_INVOICE, LAYER_UNKNOWN };

static enum field_layer tlv_layer(bigsize_t num)
{
	/* BOLT #12:
	 * A writer of an offer:
	 * - MUST NOT set any TLV fields outside the inclusive ranges:
              1 to 79 and 1000000000 to 1999999999. */
	if ((num >= 1 && num <= 79)
	    || (num >= 1000000000 && num <= 1999999999))
		return LAYER_OFFER;
	/* BOLT #12:
	 * ## Requirements for Invoice Requests
	 * The writer:
	 *...
	 * MUST NOT set any non-signature TLV fields outside the inclusive
	 * ranges: 0 to 159 and 1000000000 to 2999999999
	 */
	if ((num >= 80 && num <= 159)
	    || (num >= 2000000000 && num <= 2999999999))
		return LAYER_INVREQ;
	/* BOLT #12:
	 * *signature TLV elements*: TLV types 240 through 1000 (inclusive)
	 */
	/* So, by implication, <= 239 is the invoice field, BUT we also
	 * copy the signature field */
	if ((num >= 160 && num <= 239)
	    || (num >= 3000000000 && num <= 3999999999)
	    || num == TLV_INVOICE_SIGNATURE)
		return LAYER_INVOICE;

	return LAYER_UNKNOWN;
}

static bool u64arr_contains(const bigsize_t *types, bigsize_t type)
{
	for (size_t i = 0; i < tal_count(types); i++) {
		if (types[i] == type)
			return true;
	}
	return false;
}

/* Callback for make_unsigned_proof: include field if in include_types array. */
static bool include_field_fn(const struct tlv_field *f, bigsize_t *types)
{
	return u64arr_contains(types, f->numtype);
}

struct one_proof {
	struct createproof_state *state;

	const struct tlv_invoice *inv;
	struct preimage preimage;
	struct tlv_payer_proof *pptlv;
	/* Filled in as sigs arrive */
	const char *encoded;
};

/* Describes the decoded form of the `invstring` parameter. */
enum bolt12_invtype { INVTYPE_INVOICE, INVTYPE_OFFER, INVTYPE_BIP353 };

struct bolt12_invinfo {
	const char *invstring;  /* canonicalized */
	enum bolt12_invtype type;
	/* INVTYPE_OFFER: */
	struct sha256 offer_id;
	/* INVTYPE_BIP353: */
	struct bip_353_name bip353;
};

struct createproof_state {
	const char *note;
	bigsize_t *include_types;  /* tal_arr */

	struct bolt12_invinfo *invinfo;

	/* One for each proof we're getting sigs for */
	struct one_proof **proofs;
	/* Count down to how many sigs remaining */
	size_t n_outstanding;
};

/* Parse a 64-byte bip340 signature from a JSON hex token. */
static bool json_to_bip340sig(const char *buf, const jsmntok_t *tok,
			      struct bip340sig *sig)
{
	return hex_decode(buf + tok->start, tok->end - tok->start,
			  sig->u8, sizeof(sig->u8));
}

static void json_add_tlv_arr_level(struct json_stream *js,
				   const char *fieldname,
				   enum field_layer layer,
				   const struct tlv_field *fields)
{
	json_array_start(js, fieldname);
	for (size_t i = 0; i < tal_count(fields); i++) {
		const char *name;

		if (tlv_layer(fields[i].numtype) != layer)
			continue;

		name = tlv_type_name(fields[i].numtype);
		if (name)
			json_add_string(js, NULL, name);
		else
			json_add_u64(js, NULL, fields[i].numtype);
	}
	json_array_end(js);
}

/* Output the fields_included arrays for a payer_proof TLV. */
static void json_add_fields_included(struct json_stream *js,
				     const struct tlv_payer_proof *pptlv)
{
	json_add_tlv_arr_level(js, "offer_fields_included",
			       LAYER_OFFER, pptlv->fields);
	json_add_tlv_arr_level(js, "invreq_fields_included",
			       LAYER_INVREQ, pptlv->fields);
	json_add_tlv_arr_level(js, "invoice_fields_included",
			       LAYER_INVOICE, pptlv->fields);
}

static struct command_result *payersign_done(struct command *cmd,
					     const char *method UNNEEDED,
					     const char *buf,
					     const jsmntok_t *result,
					     struct one_proof *proof)
{
	struct createproof_state *state = proof->state;
	const jsmntok_t *sigtok;

	proof->pptlv->proof_signature = tal(proof->pptlv, struct bip340sig);
	sigtok = json_get_member(buf, result, "signature");
	if (!sigtok)
		plugin_err(cmd->plugin, "payersign: no signature in result");
	if (!json_to_bip340sig(buf, sigtok, proof->pptlv->proof_signature))
		plugin_err(cmd->plugin, "payersign: bad signature hex");

	/* Encode as lnp1... string */
	proof->encoded = payer_proof_encode(proof, proof->pptlv);

	/* When all outstanding are done, output all proofs and finish */
	if (--state->n_outstanding != 0)
		return command_still_pending(cmd);

	struct json_stream *response = jsonrpc_stream_success(cmd);
	json_array_start(response, "proofs");
	for (size_t i = 0; i < tal_count(state->proofs); i++) {
		json_object_start(response, NULL);
		json_add_string(response, "bolt12", state->proofs[i]->encoded);
		json_add_fields_included(response, state->proofs[i]->pptlv);
		json_object_end(response);
	}
	json_array_end(response);
	return command_finished(cmd, response);
}

static struct command_result *listsendpays_done(struct command *cmd,
						const char *method UNNEEDED,
						const char *buf,
						const jsmntok_t *result,
						struct createproof_state *state)
{
	const jsmntok_t *arr, *t;
	size_t i;

	arr = json_get_member(buf, result, "payments");
	if (!arr || arr->type != JSMN_ARRAY)
		return command_fail(cmd, LIGHTNINGD,
				    "Unexpected listsendpays result");

	state->proofs = tal_arr(state, struct one_proof *, 0);

	json_for_each_arr(i, t, arr) {
		const jsmntok_t *statustok, *b12tok, *preimagetok;
		const char *b12str;
		struct tlv_invoice *inv;
		struct preimage preimage;
		const char *fail;
		struct one_proof *proof;
		struct sha256 mroot;
		struct out_req *req;

		statustok = json_get_member(buf, t, "status");
		if (!statustok || !json_tok_streq(buf, statustok, "complete"))
			continue;

		preimagetok = json_get_member(buf, t, "payment_preimage");
		if (!preimagetok)
			continue;
		if (!json_to_preimage(buf, preimagetok, &preimage))
			continue;

		b12tok = json_get_member(buf, t, "bolt12");
		if (!b12tok)
			continue;

		b12str = json_strdup(tmpctx, buf, b12tok);
		inv = invoice_decode(tmpctx, b12str, strlen(b12str),
				     NULL, chainparams, &fail);
		if (!inv)
			continue;

		switch (state->invinfo->type) {
		case INVTYPE_OFFER: {
			/* offer_id matching means invoice is for this offer */
			struct sha256 inv_offer_id;
			invoice_offer_id(inv, &inv_offer_id);
			if (!sha256_eq(&state->invinfo->offer_id, &inv_offer_id))
				continue;
			break;
		}
		case INVTYPE_BIP353:
			if (!inv->invreq_bip_353_name)
				continue;
			if (!memeq(state->invinfo->bip353.name,
				   tal_bytelen(state->invinfo->bip353.name),
				   inv->invreq_bip_353_name->name,
				   tal_bytelen(inv->invreq_bip_353_name->name)))
				continue;
			if (!memeq(state->invinfo->bip353.domain,
				   tal_bytelen(state->invinfo->bip353.domain),
				   inv->invreq_bip_353_name->domain,
				   tal_bytelen(inv->invreq_bip_353_name->domain)))
				continue;
			break;
		case INVTYPE_INVOICE:
			/* We already told listsendpays to only give us this invoice */
			break;
		}

		proof = tal(state->proofs, struct one_proof);
		proof->state = state;
		proof->inv = tal_steal(proof, inv);
		proof->preimage = preimage;
		proof->pptlv = make_unsigned_proof(proof,
						   proof->inv,
						   &proof->preimage,
						   state->note,
						   include_field_fn,
						   state->include_types);
		bolt12_payer_proof_merkle(proof->pptlv, &mroot);

		req = jsonrpc_request_start(cmd, "payersign",
					    &payersign_done,
					    &plugin_broken_cb,
					    proof);
		json_add_string(req->js, "messagename", "payer_proof");
		json_add_string(req->js, "fieldname", "proof_signature");
		json_add_sha256(req->js, "merkle", &mroot);
		json_add_hex(req->js, "tweak",
			     proof->inv->invreq_metadata,
			     tal_bytelen(proof->inv->invreq_metadata));
		send_outreq(req);

		tal_arr_expand(&state->proofs, proof);
	}

	state->n_outstanding = tal_count(state->proofs);
	if (state->n_outstanding == 0)
		return command_fail(cmd, CREATEPROOF_NO_PAYMENT,
				    "No successful payment found for that invstring");

	return command_still_pending(cmd);
}

/* param_ callback for the `include` array parameter.
 * Accepts an array of field name strings or u64 numbers.
 * Always prepends the mandatory_types. */
static struct command_result *param_include_types(struct command *cmd,
						  const char *name,
						  const char *buf,
						  const jsmntok_t *tok,
						  bigsize_t **types)
{
	const jsmntok_t *t;
	size_t i;

	if (tok->type != JSMN_ARRAY)
		return command_fail_badparam(cmd, name, buf, tok,
					     "Expected array");

	*types = tal_arr(cmd, bigsize_t, 0);

	/* Always start with mandatory types */
	for (size_t j = 0; j < ARRAY_SIZE(mandatory_types); j++)
		tal_arr_expand(types, mandatory_types[j]);

	/* Add user-specified fields, skipping duplicates */
	json_for_each_arr(i, t, tok) {
		bigsize_t num;

		if (t->type == JSMN_STRING) {
			char *fname = json_strdup(tmpctx, buf, t);
			num = tlv_name_to_num(fname);
			if (num == UINT64_MAX)
				return command_fail_badparam(cmd, name, buf, t,
							     "Unknown field name");
		} else if (t->type == JSMN_PRIMITIVE) {
			u64 v;
			if (!json_to_u64(buf, t, &v))
				return command_fail_badparam(cmd, name, buf, t,
							     "Expected name or number");
			num = v;
		} else {
			return command_fail_badparam(cmd, name, buf, t,
						     "Expected name or number");
		}

		if (!u64arr_contains(*types, num))
			tal_arr_expand(types, num);
	}

	return NULL;
}

/* param_ callback for `invstring`: canonicalizes and decodes offer/BIP353. */
static struct command_result *param_bolt12_invstring(struct command *cmd,
						     const char *name,
						     const char *buf,
						     const jsmntok_t *tok,
						     struct bolt12_invinfo **invinfo)
{
	const char *invstring = to_canonical_invstr(cmd, json_strdup(tmpctx, buf, tok));
	const char *fail;

	*invinfo = tal(cmd, struct bolt12_invinfo);
	(*invinfo)->invstring = invstring;

	if (strstarts(invstring, "lni1")) {
		(*invinfo)->type = INVTYPE_INVOICE;
	} else if (strstarts(invstring, "lno1")) {
		struct tlv_offer *offer;
		offer = offer_decode(tmpctx, invstring, strlen(invstring),
				     NULL, chainparams, &fail);
		if (!offer)
			return command_fail_badparam(cmd, name, buf, tok,
						     tal_fmt(tmpctx, "Invalid offer: %s", fail));
		offer_offer_id(offer, &(*invinfo)->offer_id);
		(*invinfo)->type = INVTYPE_OFFER;
	} else if (strchr(invstring, '@')) {
		/* BOLT #12:
		 *  - if it received the offer from which it constructed this
		 *    `invoice_request` using BIP 353 resolution:
		 *     - MUST include `invreq_bip_353_name` with,
		 *       - `name` set to the post-₿, pre-@ part of the BIP 353 HRN,
		 *       - `domain` set to the post-@ part of the BIP 353 HRN.
		 */
		char *str = json_strdup(tmpctx, buf, tok);
		char *at;

		if (!utf8_check(str, strlen(str)))
			return command_fail_badparam(cmd, name, buf, tok,
						     "Invalid UTF-8");
		/* Strip ₿ if present (0xE2 0x82 0xBF) */
		if (strstarts(str, ""))
			str += strlen("");
		at = strchr(str, '@');
		if (!at)
			return command_fail_badparam(cmd, name, buf, tok,
						     "Missing @ in BIP353 address");
		(*invinfo)->bip353.name
			= tal_dup_arr(*invinfo, u8, (const u8 *)str,
				      at - str, 0);
		(*invinfo)->bip353.domain
			= tal_dup_arr(*invinfo, u8, (const u8 *)(at + 1),
				      strlen(at + 1), 0);
		(*invinfo)->type = INVTYPE_BIP353;
	} else {
		return command_fail_badparam(cmd, name, buf, tok,
					     "Expected bolt12 invoice (lni1...), "
					     "offer (lno1...), or user@domain");
	}
	return NULL;
}

struct command_result *json_createproof(struct command *cmd,
					const char *buffer,
					const jsmntok_t *params)
{
	struct createproof_state *state;
	struct out_req *req;

	state = tal(cmd, struct createproof_state);
	if (!param(cmd, buffer, params,
		   p_req("invstring", param_bolt12_invstring, &state->invinfo),
		   p_opt("note", param_string, &state->note),
		   p_opt("include", param_include_types, &state->include_types),
		   NULL))
		return command_param_failed();

	if (!state->include_types) {
		/* Default: mandatory + extras */
		state->include_types = tal_arr(state, bigsize_t, 0);
		for (size_t i = 0; i < ARRAY_SIZE(mandatory_types); i++)
			tal_arr_expand(&state->include_types, mandatory_types[i]);
		for (size_t i = 0; i < ARRAY_SIZE(default_extras); i++)
			tal_arr_expand(&state->include_types, default_extras[i]);
	}

	/* Look up completed payments.
	 * For bolt12 invoices, listsendpays filters by payment_hash.
	 * For offers and BIP353, we get all and filter in the callback. */
	req = jsonrpc_request_start(cmd, "listsendpays",
				    &listsendpays_done,
				    &forward_error,
				    state);
	if (state->invinfo->type == INVTYPE_INVOICE)
		json_add_string(req->js, "bolt11", state->invinfo->invstring);
	json_add_string(req->js, "status", "complete");
	return send_outreq(req);
}