DEVELOPER: allow fetchinvoice to force they payer_secret.

We usually assume we're fetching an invoice we are going to pay, so we
look up the previous payment for the payer key, and other sanity
checks.

This adds a developer option to fetchinvoice, which allows it to force
its own payer key, which it uses to sign directly and bypasses these
checks.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell
2021-07-05 15:53:29 +09:30
committed by neil saitug
parent d4c441f1d7
commit a29847f0c6
3 changed files with 101 additions and 8 deletions

View File

@@ -99,7 +99,11 @@ void merkle_tlv(const struct tlv_field *fields, struct sha256 *merkle)
arr[n++] = merkle_pair(s, lnall);
}
*merkle = merkle_recurse(arr, n);
/* This should never happen, but define it as lnall in this case */
if (n == 0)
*merkle = lnall;
else
*merkle = merkle_recurse(arr, n);
tal_free(arr);
}

View File

@@ -62,19 +62,26 @@ static struct sent *find_sent(const struct pubkey *blinding)
return NULL;
}
static const char *field_diff_(const tal_t *a, const tal_t *b,
static const char *field_diff_(struct plugin *plugin,
const tal_t *a, const tal_t *b,
const char *fieldname)
{
/* One is set and the other isn't? */
if ((a == NULL) != (b == NULL))
if ((a == NULL) != (b == NULL)) {
plugin_log(plugin, LOG_DBG, "field_diff %s: a is %s, b is %s",
fieldname, a ? "set": "unset", b ? "set": "unset");
return fieldname;
if (!memeq(a, tal_bytelen(a), b, tal_bytelen(b)))
}
if (!memeq(a, tal_bytelen(a), b, tal_bytelen(b))) {
plugin_log(plugin, LOG_DBG, "field_diff %s: a=%s, b=%s",
fieldname, tal_hex(tmpctx, a), tal_hex(tmpctx, b));
return fieldname;
}
return NULL;
}
#define field_diff(a, b, fieldname) \
field_diff_(a->fieldname, b->fieldname, #fieldname)
#define field_diff(a, b, fieldname) \
field_diff_((cmd)->plugin, a->fieldname, b->fieldname, #fieldname)
/* Returns true if b is a with something appended. */
static bool description_is_appended(const char *a, const char *b)
@@ -824,6 +831,66 @@ static struct command_result *invreq_done(struct command *cmd,
return sendinvreq_after_connect(cmd, NULL, NULL, sent);
}
/* If they hand us the payer secret, we sign it directly, bypassing checks
* about periods etc. */
static struct command_result *
force_payer_secret(struct command *cmd,
struct sent *sent,
struct tlv_invoice_request *invreq,
const struct secret *payer_secret)
{
struct sha256 merkle, sha;
bool try_connect;
secp256k1_keypair kp;
u8 *msg;
const u8 *p;
size_t len;
if (secp256k1_keypair_create(secp256k1_ctx, &kp, payer_secret->data) != 1)
return command_fail(cmd, LIGHTNINGD, "Bad payer_secret");
invreq->payer_key = tal(invreq, struct pubkey32);
/* Docs say this only happens if arguments are invalid! */
if (secp256k1_keypair_xonly_pub(secp256k1_ctx,
&invreq->payer_key->pubkey, NULL,
&kp) != 1)
plugin_err(cmd->plugin,
"secp256k1_keypair_pub failed on %s?",
type_to_string(tmpctx, struct secret, payer_secret));
/* Linearize populates ->fields */
msg = tal_arr(tmpctx, u8, 0);
towire_invoice_request(&msg, invreq);
p = msg;
len = tal_bytelen(msg);
sent->invreq = tlv_invoice_request_new(cmd);
if (!fromwire_invoice_request(&p, &len, sent->invreq))
plugin_err(cmd->plugin,
"Could not remarshall invreq %s", tal_hex(tmpctx, msg));
merkle_tlv(sent->invreq->fields, &merkle);
sighash_from_merkle("invoice_request", "payer_signature", &merkle, &sha);
sent->invreq->payer_signature = tal(invreq, struct bip340sig);
if (!secp256k1_schnorrsig_sign(secp256k1_ctx,
sent->invreq->payer_signature->u8,
sha.u.u8,
&kp,
NULL, NULL)) {
return command_fail(cmd, LIGHTNINGD,
"Failed to sign with payer_secret");
}
sent->path = path_to_node(sent, get_gossmap(cmd->plugin),
sent->offer->node_id,
&try_connect);
if (try_connect)
return connect_direct(cmd, &sent->path[0],
sendinvreq_after_connect, sent);
return sendinvreq_after_connect(cmd, NULL, NULL, sent);
}
/* Fetches an invoice for this offer, and makes sure it corresponds. */
static struct command_result *json_fetchinvoice(struct command *cmd,
const char *buffer,
@@ -834,6 +901,7 @@ static struct command_result *json_fetchinvoice(struct command *cmd,
struct out_req *req;
struct tlv_invoice_request *invreq;
struct sent *sent = tal(cmd, struct sent);
struct secret *payer_secret = NULL;
u32 *timeout;
invreq = tlv_invoice_request_new(sent);
@@ -848,6 +916,9 @@ static struct command_result *json_fetchinvoice(struct command *cmd,
p_opt("recurrence_label", param_string, &rec_label),
p_opt_def("timeout", param_number, &timeout, 60),
p_opt("payer_note", param_string, &payer_note),
#if DEVELOPER
p_opt("payer_secret", param_secret, &payer_secret),
#endif
NULL))
return command_param_failed();
@@ -964,8 +1035,8 @@ static struct command_result *json_fetchinvoice(struct command *cmd,
}
/* recurrence_label uniquely identifies this series of
* payments. */
if (!rec_label)
* payments (unless they supply secret themselves)! */
if (!rec_label && !payer_secret)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"needs recurrence_label");
} else {
@@ -1004,6 +1075,11 @@ static struct command_result *json_fetchinvoice(struct command *cmd,
payer_note, strlen(payer_note),
0);
/* They can provide a secret, and we don't assume it's our job
* to pay. */
if (payer_secret)
return force_payer_secret(cmd, sent, invreq, payer_secret);
/* Make the invoice request (fills in payer_key and payer_info) */
req = jsonrpc_request_start(cmd->plugin, cmd, "createinvoicerequest",
&invreq_done,

View File

@@ -254,6 +254,13 @@ static struct command_result *check_period(struct command *cmd,
if (err)
return err;
period_idx += *ir->invreq->recurrence_start;
/* BOLT-offers #12:
* - MUST set (or not set) `recurrence_start` exactly as the
* invoice_request did.
*/
ir->inv->recurrence_start
= tal_dup(ir->inv, u32, ir->invreq->recurrence_start);
} else {
/* BOLT-offers #12:
*
@@ -734,10 +741,15 @@ static struct command_result *listoffers_done(struct command *cmd,
* - otherwise (the offer had no `recurrence`):
* - MUST fail the request if there is a `recurrence_counter`
* field.
* - MUST fail the request if there is a `recurrence_start`
* field.
*/
err = invreq_must_not_have(cmd, ir, recurrence_counter);
if (err)
return err;
err = invreq_must_not_have(cmd, ir, recurrence_start);
if (err)
return err;
}
ir->inv = tlv_invoice_new(cmd);
@@ -770,6 +782,7 @@ static struct command_result *listoffers_done(struct command *cmd,
*/
if (ir->offer->quantity_min || ir->offer->quantity_max)
ir->inv->quantity = tal_dup(ir->inv, u64, ir->invreq->quantity);
/* BOLT-offers #12:
* - MUST set `payer_key` exactly as the invoice_request did.
*/