forked from ElementsProject/lightning
-
Notifications
You must be signed in to change notification settings - Fork 0
/
offers_inv_hook.c
337 lines (294 loc) · 10.8 KB
/
offers_inv_hook.c
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
#include "config.h"
#include <ccan/mem/mem.h>
#include <ccan/tal/str/str.h>
#include <common/bolt12_merkle.h>
#include <common/json_stream.h>
#include <plugins/offers.h>
#include <plugins/offers_inv_hook.h>
#include <secp256k1_schnorrsig.h>
/* We need to keep the reply path around so we can reply if error */
struct inv {
struct tlv_invoice *inv;
struct sha256 invreq_id;
/* May be NULL */
struct blinded_path *reply_path;
/* The invreq, once we've looked it up. */
struct tlv_invoice_request *invreq;
};
static struct command_result *WARN_UNUSED_RESULT
fail_inv_level(struct command *cmd,
const struct inv *inv,
enum log_level l,
const char *fmt, va_list ap)
{
char *full_fmt, *msg;
struct tlv_onionmsg_tlv *payload;
struct tlv_invoice_error *err;
full_fmt = tal_fmt(tmpctx, "Failed invoice");
if (inv->inv) {
tal_append_fmt(&full_fmt, " %s",
invoice_encode(tmpctx, inv->inv));
}
tal_append_fmt(&full_fmt, ": %s", fmt);
msg = tal_vfmt(tmpctx, full_fmt, ap);
plugin_log(cmd->plugin, l, "%s", msg);
/* Only reply if they gave us a path */
if (!inv->reply_path)
return command_hook_success(cmd);
/* Don't send back internal error details. */
if (l == LOG_BROKEN)
msg = "Internal error";
/* Get path (maybe connect) to send reply */
err = tlv_invoice_error_new(cmd);
/* Remove NUL terminator */
err->error = tal_dup_arr(err, char, msg, strlen(msg), 0);
/* FIXME: Add suggested_value / erroneous_field! */
payload = tlv_onionmsg_tlv_new(NULL);
payload->invoice_error = tal_arr(payload, u8, 0);
towire_tlv_invoice_error(&payload->invoice_error, err);
return send_onion_reply(cmd, inv->reply_path, payload);
}
static struct command_result *WARN_UNUSED_RESULT
fail_inv(struct command *cmd,
const struct inv *inv,
const char *fmt, ...)
{
va_list ap;
struct command_result *ret;
va_start(ap, fmt);
ret = fail_inv_level(cmd, inv, LOG_DBG, fmt, ap);
va_end(ap);
return ret;
}
static struct command_result *WARN_UNUSED_RESULT
fail_internalerr(struct command *cmd,
const struct inv *inv,
const char *fmt, ...)
{
va_list ap;
struct command_result *ret;
va_start(ap, fmt);
ret = fail_inv_level(cmd, inv, LOG_BROKEN, fmt, ap);
va_end(ap);
return ret;
}
static struct command_result *pay_done(struct command *cmd,
const char *buf,
const jsmntok_t *result,
struct inv *inv)
{
struct amount_msat msat = amount_msat(*inv->inv->invoice_amount);
plugin_log(cmd->plugin, LOG_INFORM,
"Payed out %s for invreq %s: %.*s",
fmt_amount_msat(tmpctx, msat),
fmt_sha256(tmpctx, &inv->invreq_id),
json_tok_full_len(result),
json_tok_full(buf, result));
return command_hook_success(cmd);
}
static struct command_result *pay_error(struct command *cmd,
const char *buf,
const jsmntok_t *error,
struct inv *inv)
{
const jsmntok_t *msgtok = json_get_member(buf, error, "message");
return fail_inv(cmd, inv, "pay attempt failed: %.*s",
json_tok_full_len(msgtok),
json_tok_full(buf, msgtok));
}
static struct command_result *listinvreqs_done(struct command *cmd,
const char *buf,
const jsmntok_t *result,
struct inv *inv)
{
const jsmntok_t *arr = json_get_member(buf, result, "invoicerequests");
const jsmntok_t *activetok;
bool active;
struct amount_msat amt;
struct out_req *req;
struct sha256 merkle, sighash;
/* BOLT-offers #12:
* A reader of an invoice:
*...
* - if the invoice is a response to an `invoice_request`:
* - MUST reject the invoice if all fields less than type 160 do not exactly match the `invoice_request`.
* - if `offer_node_id` is present (invoice_request for an offer):
* - MUST reject the invoice if `invoice_node_id` is not equal to `offer_node_id`.
* - otherwise (invoice_request without an offer):
* - MAY reject the invoice if it cannot confirm that `invoice_node_id` is correct, out-of-band.
*
* - otherwise: (a invoice presented without being requested, eg. scanned by user):
*/
/* Since the invreq_id hashes all fields < 160, we know it matches */
if (arr->size == 0)
return fail_inv(cmd, inv, "Unknown invoice_request %s",
fmt_sha256(tmpctx, &inv->invreq_id));
activetok = json_get_member(buf, arr + 1, "active");
if (!activetok) {
return fail_internalerr(cmd, inv,
"Missing active: %.*s",
json_tok_full_len(arr),
json_tok_full(buf, arr));
}
json_to_bool(buf, activetok, &active);
if (!active)
return fail_inv(cmd, inv, "invoice_request no longer available");
/* We only save ones without offers to the db! */
assert(!inv->inv->offer_node_id);
/* BOLT-offers #12:
* - MUST reject the invoice if `signature` is not a valid signature
* using `invoice_node_id` as described in [Signature
* Calculation](#signature-calculation).
*/
if (!inv->inv->signature)
return fail_inv(cmd, inv, "invoice missing signature");
merkle_tlv(inv->inv->fields, &merkle);
sighash_from_merkle("invoice", "signature", &merkle, &sighash);
if (!check_schnorr_sig(&sighash, &inv->inv->invoice_node_id->pubkey, inv->inv->signature))
return fail_inv(cmd, inv, "invalid invoice signature");
/* BOLT-offers #12:
* - SHOULD confirm authorization if `invoice_amount`.`msat` is not
* within the amount range authorized.
*/
/* Because there's no offer, we had to set invreq_amount */
if (*inv->inv->invoice_amount > *inv->inv->invreq_amount)
return fail_inv(cmd, inv, "invoice amount is too large");
/* FIXME: Create a hook for validating the invoice_node_id! */
amt = amount_msat(*inv->inv->invoice_amount);
plugin_log(cmd->plugin, LOG_INFORM,
"Attempting payment of %s for invoice_request %s",
fmt_amount_msat(tmpctx, amt),
fmt_sha256(tmpctx, &inv->invreq_id));
req = jsonrpc_request_start(cmd->plugin, cmd, "pay",
pay_done, pay_error, inv);
json_add_string(req->js, "bolt11", invoice_encode(tmpctx, inv->inv));
json_add_sha256(req->js, "localinvreqid", &inv->invreq_id);
return send_outreq(cmd->plugin, req);
}
static struct command_result *listinvreqs_error(struct command *cmd,
const char *buf,
const jsmntok_t *err,
struct inv *inv)
{
return fail_internalerr(cmd, inv,
"listinvoicerequests gave JSON error: %.*s",
json_tok_full_len(err),
json_tok_full(buf, err));
}
struct command_result *handle_invoice(struct command *cmd,
const u8 *invbin,
struct blinded_path *reply_path STEALS,
const struct secret *secret)
{
size_t len = tal_count(invbin);
struct inv *inv = tal(cmd, struct inv);
struct out_req *req;
int bad_feature;
u64 invexpiry;
inv->reply_path = tal_steal(inv, reply_path);
inv->inv = fromwire_tlv_invoice(cmd, &invbin, &len);
if (!inv->inv) {
return fail_inv(cmd, inv,
"Invalid invoice %s",
tal_hex(tmpctx, invbin));
}
invoice_invreq_id(inv->inv, &inv->invreq_id);
/* We never publish invoice_requests with a reply path, so replies via
* a path are invalid */
if (secret) {
if (command_dev_apis(cmd))
return fail_inv(cmd, inv, "Unexpected blinded path");
/* Normally, "I don't know what you're talking about!" */
return fail_inv(cmd, inv, "Unknown invoice_request %s",
fmt_sha256(tmpctx, &inv->invreq_id));
}
/* 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 (!inv->inv->invoice_amount)
return fail_inv(cmd, inv, "Missing invoice_amount");
if (!inv->inv->invoice_created_at)
return fail_inv(cmd, inv, "Missing invoice_created_at");
if (!inv->inv->invoice_payment_hash)
return fail_inv(cmd, inv, "Missing invoice_payment_hash");
if (!inv->inv->invoice_node_id)
return fail_inv(cmd, inv, "Missing invoice_node_id");
/* BOLT-offers #12:
* A reader of an invoice:
*...
* - 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.
*/
bad_feature = features_unsupported(plugin_feature_set(cmd->plugin),
inv->inv->invoice_features,
BOLT12_INVOICE_FEATURE);
if (bad_feature != -1) {
return fail_inv(cmd, inv,
"Unsupported invoice feature %i",
bad_feature);
}
/* BOLT-offers #12:
* A reader of an invoice:
*...
* - 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 (inv->inv->invoice_relative_expiry)
invexpiry = *inv->inv->invoice_created_at + *inv->inv->invoice_relative_expiry;
else
invexpiry = *inv->inv->invoice_created_at + BOLT12_DEFAULT_REL_EXPIRY;
if (time_now().ts.tv_sec > invexpiry)
return fail_inv(cmd, inv, "Expired invoice");
/* BOLT-offers #12:
* A reader of an invoice:
*...
* - MUST reject the invoice if `invoice_paths` is not present or is empty.
* - 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 (!inv->inv->invoice_paths)
return fail_inv(cmd, inv, "Missing invoice_paths");
if (!inv->inv->invoice_blindedpay)
return fail_inv(cmd, inv, "Missing invoice_blindedpay");
if (tal_count(inv->inv->invoice_blindedpay)
!= tal_count(inv->inv->invoice_paths))
return fail_inv(cmd, inv,
"Mismatch between invoice_blindedpay and invoice_paths");
/* BOLT-offers #12:
* A reader of an invoice:
*...
* - For each `invoice_blindedpay`.`payinfo`:
* - MUST NOT use the corresponding `invoice_paths`.`path` if
* `payinfo`.`features` has any unknown even bits set.
* - MUST reject the invoice if this leaves no usable paths.
*/
for (size_t i = 0; i < tal_count(inv->inv->invoice_blindedpay); i++) {
bad_feature = features_unsupported(plugin_feature_set(cmd->plugin),
inv->inv->invoice_blindedpay[i]->features,
/* FIXME: Technically a different feature set? */
BOLT12_INVOICE_FEATURE);
if (bad_feature == -1)
continue;
tal_arr_remove(&inv->inv->invoice_paths, i);
tal_arr_remove(&inv->inv->invoice_blindedpay, i);
i--;
}
if (tal_count(inv->inv->invoice_paths) == 0) {
return fail_inv(cmd, inv,
"Unsupported feature for all paths (%i)",
bad_feature);
}
/* Now find the invoice_request. */
req = jsonrpc_request_start(cmd->plugin, cmd, "listinvoicerequests",
listinvreqs_done, listinvreqs_error, inv);
json_add_sha256(req->js, "invreq_id", &inv->invreq_id);
return send_outreq(cmd->plugin, req);
}