1 #include <ccan/mem/mem.h>
2 #include <ccan/tal/str/str.h>
3 #include <common/bolt12_merkle.h>
4 #include <common/type_to_string.h>
5 #include <plugins/offers.h>
6 #include <plugins/offers_inv_hook.h>
7 #include <secp256k1_schnorrsig.h>
8
9 /* We need to keep the reply path around so we can reply if error */
10 struct inv {
11 struct tlv_invoice *inv;
12
13 const char *buf;
14 /* May be NULL */
15 const jsmntok_t *replytok;
16 struct tlv_onionmsg_payload_reply_path *reply_path;
17
18 /* The offer, once we've looked it up. */
19 struct tlv_offer *offer;
20 };
21
22 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)23 fail_inv_level(struct command *cmd,
24 const struct inv *inv,
25 enum log_level l,
26 const char *fmt, va_list ap)
27 {
28 char *full_fmt, *msg;
29 struct tlv_invoice_error *err;
30 u8 *errdata;
31
32 full_fmt = tal_fmt(tmpctx, "Failed invoice %s",
33 invoice_encode(tmpctx, inv->inv));
34 if (inv->inv->offer_id)
35 tal_append_fmt(&full_fmt, " for offer %s",
36 type_to_string(tmpctx, struct sha256,
37 inv->inv->offer_id));
38 tal_append_fmt(&full_fmt, ": %s", fmt);
39
40 msg = tal_vfmt(tmpctx, full_fmt, ap);
41 plugin_log(cmd->plugin, l, "%s", msg);
42
43 /* Only reply if they gave us a path */
44 if (!inv->replytok && !inv->reply_path)
45 return command_hook_success(cmd);
46
47 /* Don't send back internal error details. */
48 if (l == LOG_BROKEN)
49 msg = "Internal error";
50
51 err = tlv_invoice_error_new(cmd);
52 /* Remove NUL terminator */
53 err->error = tal_dup_arr(err, char, msg, strlen(msg), 0);
54 /* FIXME: Add suggested_value / erroneous_field! */
55
56 errdata = tal_arr(cmd, u8, 0);
57 towire_invoice_error(&errdata, err);
58 return send_onion_reply(cmd, inv->reply_path, inv->buf, inv->replytok,
59 "invoice_error", errdata);
60 }
61
62 static struct command_result *WARN_UNUSED_RESULT
fail_inv(struct command * cmd,const struct inv * inv,const char * fmt,...)63 fail_inv(struct command *cmd,
64 const struct inv *inv,
65 const char *fmt, ...)
66 {
67 va_list ap;
68 struct command_result *ret;
69
70 va_start(ap, fmt);
71 ret = fail_inv_level(cmd, inv, LOG_DBG, fmt, ap);
72 va_end(ap);
73
74 return ret;
75 }
76
77 static struct command_result *WARN_UNUSED_RESULT
fail_internalerr(struct command * cmd,const struct inv * inv,const char * fmt,...)78 fail_internalerr(struct command *cmd,
79 const struct inv *inv,
80 const char *fmt, ...)
81 {
82 va_list ap;
83 struct command_result *ret;
84
85 va_start(ap, fmt);
86 ret = fail_inv_level(cmd, inv, LOG_BROKEN, fmt, ap);
87 va_end(ap);
88
89 return ret;
90 }
91
92 #define inv_must_have(cmd_, i_, fld_) \
93 test_field(cmd_, i_, i_->inv->fld_ != NULL, #fld_, "missing")
94 #define inv_must_not_have(cmd_, i_, fld_) \
95 test_field(cmd_, i_, i_->inv->fld_ == NULL, #fld_, "unexpected")
96 #define inv_must_equal_offer(cmd_, i_, fld_) \
97 test_field_eq(cmd_, i_, i_->inv->fld_, i_->offer->fld_, #fld_)
98
99 static struct command_result *
test_field(struct command * cmd,const struct inv * inv,bool test,const char * fieldname,const char * what)100 test_field(struct command *cmd,
101 const struct inv *inv,
102 bool test, const char *fieldname, const char *what)
103 {
104 if (!test)
105 return fail_inv(cmd, inv, "%s %s", what, fieldname);
106 return NULL;
107 }
108
109 static struct command_result *
test_field_eq(struct command * cmd,const struct inv * inv,const tal_t * invfield,const tal_t * offerfield,const char * fieldname)110 test_field_eq(struct command *cmd,
111 const struct inv *inv,
112 const tal_t *invfield,
113 const tal_t *offerfield,
114 const char *fieldname)
115 {
116 if (invfield && !offerfield)
117 return fail_inv(cmd, inv, "Unexpected %s", fieldname);
118 if (!invfield && offerfield)
119 return fail_inv(cmd, inv, "Expected %s", fieldname);
120 if (!memeq(invfield, tal_bytelen(invfield),
121 offerfield, tal_bytelen(offerfield)))
122 return fail_inv(cmd, inv, "Different %s", fieldname);
123 return NULL;
124 }
125
pay_done(struct command * cmd,const char * buf,const jsmntok_t * result,struct inv * inv)126 static struct command_result *pay_done(struct command *cmd,
127 const char *buf,
128 const jsmntok_t *result,
129 struct inv *inv)
130 {
131 struct amount_msat msat = amount_msat(*inv->inv->amount);
132
133 plugin_log(cmd->plugin, LOG_INFORM,
134 "Payed out %s for offer %s%s: %.*s",
135 type_to_string(tmpctx, struct amount_msat, &msat),
136 type_to_string(tmpctx, struct sha256, inv->inv->offer_id),
137 inv->offer->refund_for ? " (refund)": "",
138 json_tok_full_len(result),
139 json_tok_full(buf, result));
140 return command_hook_success(cmd);
141 }
142
pay_error(struct command * cmd,const char * buf,const jsmntok_t * error,struct inv * inv)143 static struct command_result *pay_error(struct command *cmd,
144 const char *buf,
145 const jsmntok_t *error,
146 struct inv *inv)
147 {
148 const jsmntok_t *msgtok = json_get_member(buf, error, "message");
149
150 return fail_inv(cmd, inv, "pay attempt failed: %.*s",
151 json_tok_full_len(msgtok),
152 json_tok_full(buf, msgtok));
153 }
154
listoffers_done(struct command * cmd,const char * buf,const jsmntok_t * result,struct inv * inv)155 static struct command_result *listoffers_done(struct command *cmd,
156 const char *buf,
157 const jsmntok_t *result,
158 struct inv *inv)
159 {
160 const jsmntok_t *arr = json_get_member(buf, result, "offers");
161 const jsmntok_t *offertok, *activetok, *b12tok;
162 bool active;
163 struct amount_msat amt;
164 char *fail;
165 struct out_req *req;
166 struct command_result *err;
167
168 /* BOLT-offers #12:
169 * - otherwise if `offer_id` is set:
170 * - MUST reject the invoice if the `offer_id` does not refer an
171 * unexpired offer with `send_invoice`
172 */
173 if (arr->size == 0)
174 return fail_inv(cmd, inv, "Unknown offer");
175
176 plugin_log(cmd->plugin, LOG_INFORM,
177 "Attempting payment of offer %.*s",
178 json_tok_full_len(result),
179 json_tok_full(buf, result));
180
181 offertok = arr + 1;
182 activetok = json_get_member(buf, offertok, "active");
183 if (!activetok) {
184 return fail_internalerr(cmd, inv,
185 "Missing active: %.*s",
186 json_tok_full_len(offertok),
187 json_tok_full(buf, offertok));
188 }
189 json_to_bool(buf, activetok, &active);
190 if (!active)
191 return fail_inv(cmd, inv, "Offer no longer available");
192
193 b12tok = json_get_member(buf, offertok, "bolt12");
194 if (!b12tok) {
195 return fail_internalerr(cmd, inv,
196 "Missing bolt12: %.*s",
197 json_tok_full_len(offertok),
198 json_tok_full(buf, offertok));
199 }
200 inv->offer = offer_decode(inv,
201 buf + b12tok->start,
202 b12tok->end - b12tok->start,
203 plugin_feature_set(cmd->plugin),
204 chainparams, &fail);
205 if (!inv->offer) {
206 return fail_internalerr(cmd, inv,
207 "Invalid offer: %s (%.*s)",
208 fail,
209 json_tok_full_len(offertok),
210 json_tok_full(buf, offertok));
211 }
212
213 if (inv->offer->absolute_expiry
214 && time_now().ts.tv_sec >= *inv->offer->absolute_expiry) {
215 /* FIXME: do deloffer to disable it */
216 return fail_inv(cmd, inv, "Offer expired");
217 }
218
219 if (!inv->offer->send_invoice) {
220 return fail_inv(cmd, inv, "Offer did not expect invoice");
221 }
222
223 /* BOLT-offers #12:
224 * - MUST reject the invoice unless the following fields are equal
225 * or unset exactly as they are in the `offer`:
226 * - `refund_for`
227 * - `description`
228 */
229 err = inv_must_equal_offer(cmd, inv, refund_for);
230 if (err)
231 return err;
232 err = inv_must_equal_offer(cmd, inv, description);
233 if (err)
234 return err;
235
236 /* BOLT-offers #12:
237 * - if the offer had a `quantity_min` or `quantity_max` field:
238 * - MUST fail the request if there is no `quantity` field.
239 * - MUST fail the request if there is `quantity` is not within
240 * that (inclusive) range.
241 * - otherwise:
242 * - MUST fail the request if there is a `quantity` field.
243 */
244 if (inv->offer->quantity_min || inv->offer->quantity_max) {
245 err = inv_must_have(cmd, inv, quantity);
246 if (err)
247 return err;
248
249 if (inv->offer->quantity_min &&
250 *inv->inv->quantity < *inv->offer->quantity_min) {
251 return fail_inv(cmd, inv,
252 "quantity %"PRIu64 " < %"PRIu64,
253 *inv->inv->quantity,
254 *inv->offer->quantity_min);
255 }
256
257 if (inv->offer->quantity_max &&
258 *inv->inv->quantity > *inv->offer->quantity_max) {
259 return fail_inv(cmd, inv,
260 "quantity %"PRIu64" > %"PRIu64,
261 *inv->inv->quantity,
262 *inv->offer->quantity_max);
263 }
264 } else {
265 err = inv_must_not_have(cmd, inv, quantity);
266 if (err)
267 return err;
268 }
269
270 /* BOLT-offers #12:
271 * - MUST reject the invoice if `msat` is not present.
272 */
273 err = inv_must_have(cmd, inv, amount);
274 if (err)
275 return err;
276
277 /* FIXME: Handle alternate currency conversion here! */
278 if (inv->offer->currency)
279 return fail_inv(cmd, inv, "FIXME: support currency");
280
281 amt = amount_msat(*inv->inv->amount);
282 /* If you send an offer without an amount, you want to give away
283 * unlimited money. Err, ok? */
284 if (inv->offer->amount) {
285 struct amount_msat expected = amount_msat(*inv->offer->amount);
286
287 /* We could allow invoices for less, I suppose. */
288 if (!amount_msat_eq(expected, amt))
289 return fail_inv(cmd, inv, "Expected invoice for %s",
290 fmt_amount_msat(tmpctx, expected));
291 }
292
293 plugin_log(cmd->plugin, LOG_INFORM,
294 "Attempting payment of %s for offer %s%s",
295 type_to_string(tmpctx, struct amount_msat, &amt),
296 type_to_string(tmpctx, struct sha256, inv->inv->offer_id),
297 inv->offer->refund_for ? " (refund)": "");
298
299 req = jsonrpc_request_start(cmd->plugin, cmd, "pay",
300 pay_done, pay_error, inv);
301 json_add_string(req->js, "bolt11", invoice_encode(tmpctx, inv->inv));
302 json_add_sha256(req->js, "localofferid", inv->inv->offer_id);
303 return send_outreq(cmd->plugin, req);
304 }
305
listoffers_error(struct command * cmd,const char * buf,const jsmntok_t * err,struct inv * inv)306 static struct command_result *listoffers_error(struct command *cmd,
307 const char *buf,
308 const jsmntok_t *err,
309 struct inv *inv)
310 {
311 return fail_internalerr(cmd, inv,
312 "listoffers gave JSON error: %.*s",
313 json_tok_full_len(err),
314 json_tok_full(buf, err));
315 }
316
handle_invoice(struct command * cmd,const char * buf,const jsmntok_t * invtok,const jsmntok_t * replytok,struct tlv_onionmsg_payload_reply_path * reply_path)317 struct command_result *handle_invoice(struct command *cmd,
318 const char *buf,
319 const jsmntok_t *invtok,
320 const jsmntok_t *replytok,
321 struct tlv_onionmsg_payload_reply_path *reply_path)
322 {
323 const u8 *invbin = json_tok_bin_from_hex(cmd, buf, invtok);
324 size_t len = tal_count(invbin);
325 struct inv *inv = tal(cmd, struct inv);
326 struct out_req *req;
327 struct command_result *err;
328 int bad_feature;
329 struct sha256 m, shash;
330
331 if (reply_path) {
332 inv->buf = NULL;
333 inv->replytok = NULL;
334 inv->reply_path = reply_path;
335 } else {
336 /* Make a copy of entire buffer, for later. */
337 inv->buf = tal_dup_arr(inv, char, buf, replytok->end, 0);
338 inv->replytok = tal_dup_arr(inv, jsmntok_t, replytok,
339 json_next(replytok) - replytok, 0);
340 inv->reply_path = NULL;
341 }
342
343 inv->inv = tlv_invoice_new(cmd);
344 if (!fromwire_invoice(&invbin, &len, inv->inv)) {
345 return fail_inv(cmd, inv,
346 "Invalid invoice %s",
347 tal_hex(tmpctx, invbin));
348 }
349
350 /* BOLT-offers #12:
351 *
352 * The reader of an invoice_request:
353 *...
354 * - MUST fail the request if `features` contains unknown even bits.
355 */
356 bad_feature = features_unsupported(plugin_feature_set(cmd->plugin),
357 inv->inv->features,
358 BOLT11_FEATURE);
359 if (bad_feature != -1) {
360 return fail_inv(cmd, inv,
361 "Unsupported inv feature %i",
362 bad_feature);
363 }
364
365 /* BOLT-offers #12:
366 *
367 * The reader of an invoice_request:
368 *...
369 * - MUST fail the request if `chains` does not include (or imply) a
370 * supported chain.
371 */
372 if (!bolt12_chain_matches(inv->inv->chain, chainparams, inv->inv->chains)) {
373 return fail_inv(cmd, inv,
374 "Wrong chains %s",
375 tal_hex(tmpctx, inv->inv->chain));
376 }
377
378 /* BOLT-offers #12:
379 * - MUST reject the invoice if `signature` is not a valid signature
380 * using `node_id` as described in
381 * [Signature Calculation](#signature-calculation).
382 */
383 err = inv_must_have(cmd, inv, node_id);
384 if (err)
385 return err;
386
387 err = inv_must_have(cmd, inv, signature);
388 if (err)
389 return err;
390
391 merkle_tlv(inv->inv->fields, &m);
392 sighash_from_merkle("invoice", "signature", &m, &shash);
393 if (secp256k1_schnorrsig_verify(secp256k1_ctx,
394 inv->inv->signature->u8,
395 shash.u.u8,
396 &inv->inv->node_id->pubkey) != 1) {
397 return fail_inv(cmd, inv, "Bad signature");
398 }
399
400 /* We don't pay random invoices off the internet, sorry. */
401 err = inv_must_have(cmd, inv, offer_id);
402 if (err)
403 return err;
404
405 /* Now find the offer. */
406 req = jsonrpc_request_start(cmd->plugin, cmd, "listoffers",
407 listoffers_done, listoffers_error, inv);
408 json_add_sha256(req->js, "offer_id", inv->inv->offer_id);
409 return send_outreq(cmd->plugin, req);
410 }
411
412