#include "config.h"
#include <ccan/mem/mem.h>
#include <ccan/tal/str/str.h>
#include <common/bolt12_id.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>
struct inv {
struct tlv_invoice *inv;
struct sha256 invreq_id;
struct blinded_path *reply_path;
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);
if (!inv->reply_path)
return command_hook_success(cmd);
if (l == LOG_BROKEN)
msg = "Internal error";
err = tlv_invoice_error_new(cmd);
err->error = tal_dup_arr(err, char, msg, strlen(msg), 0);
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 *method,
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 *method,
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 *method,
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;
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");
assert(!inv->inv->offer_issuer_id && !inv->inv->offer_paths);
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");
if (*inv->inv->invoice_amount > *inv->inv->invreq_amount)
return fail_inv(cmd, inv, "invoice amount is too large");
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, "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(req);
}
static struct command_result *listinvreqs_error(struct command *cmd,
const char *method,
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));
}
if (secret) {
const u8 *path_secret;
struct blinded_path **invreq_paths = inv->inv->invreq_paths;
struct sha256 invreq_id_nopath;
inv->inv->invreq_paths = NULL;
invoice_invreq_id(inv->inv, &invreq_id_nopath);
inv->inv->invreq_paths = invreq_paths;
path_secret = bolt12_path_id(tmpctx, &offerblinding_base, &invreq_id_nopath);
if (!memeq(path_secret, tal_count(path_secret),
secret, sizeof(*secret))) {
if (command_dev_apis(cmd))
return fail_inv(cmd, inv, "Wrong blinded path (invreq_id_nopath = %s, path_secret = %s, secret = %s)",
fmt_sha256(tmpctx, &invreq_id_nopath),
tal_hex(tmpctx, path_secret),
fmt_secret(tmpctx, secret));
return fail_inv(cmd, inv, "Unknown invoice_request %s",
fmt_sha256(tmpctx, &inv->invreq_id));
}
} else {
if (inv->inv->invreq_paths) {
if (command_dev_apis(cmd))
return fail_inv(cmd, inv, "Expected to use invreq_path!");
return fail_inv(cmd, inv, "Unknown invoice_request %s",
fmt_sha256(tmpctx, &inv->invreq_id));
}
}
invoice_invreq_id(inv->inv, &inv->invreq_id);
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");
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);
}
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");
if (!inv->inv->invoice_paths)
return fail_inv(cmd, inv, "Missing invoice_paths");
for (size_t i = 0; i < tal_count(inv->inv->invoice_paths); i++) {
if (tal_count(inv->inv->invoice_paths[i]->path) == 0)
return fail_inv(cmd, inv, "Empty path in 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");
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,
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);
}
req = jsonrpc_request_start(cmd, "listinvoicerequests",
listinvreqs_done, listinvreqs_error, inv);
json_add_sha256(req->js, "invreq_id", &inv->invreq_id);
return send_outreq(req);
}