#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>
static const bigsize_t default_extras[] = {
TLV_INVOICE_OFFER_DESCRIPTION,
TLV_INVOICE_OFFER_ISSUER_ID,
TLV_INVOICE_INVOICE_CREATED_AT,
TLV_INVOICE_INVOICE_AMOUNT,
};
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,
};
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 },
};
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;
}
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;
}
enum field_layer { LAYER_OFFER, LAYER_INVREQ, LAYER_INVOICE, LAYER_UNKNOWN };
static enum field_layer tlv_layer(bigsize_t num)
{
if ((num >= 1 && num <= 79)
|| (num >= 1000000000 && num <= 1999999999))
return LAYER_OFFER;
if ((num >= 80 && num <= 159)
|| (num >= 2000000000 && num <= 2999999999))
return LAYER_INVREQ;
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;
}
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;
const char *encoded;
};
enum bolt12_invtype { INVTYPE_INVOICE, INVTYPE_OFFER, INVTYPE_BIP353 };
struct bolt12_invinfo {
const char *invstring;
enum bolt12_invtype type;
struct sha256 offer_id;
struct bip_353_name bip353;
};
struct createproof_state {
const char *note;
bigsize_t *include_types;
struct bolt12_invinfo *invinfo;
struct one_proof **proofs;
size_t n_outstanding;
};
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);
}
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");
proof->encoded = payer_proof_encode(proof, proof->pptlv);
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: {
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:
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);
}
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);
for (size_t j = 0; j < ARRAY_SIZE(mandatory_types); j++)
tal_arr_expand(types, mandatory_types[j]);
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;
}
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, '@')) {
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");
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) {
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]);
}
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);
}