From a29847f0c64b485ff5a7daa4c313e1652229e9de Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 5 Jul 2021 15:53:29 +0930 Subject: [PATCH] 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 --- common/bolt12_merkle.c | 6 ++- plugins/fetchinvoice.c | 90 +++++++++++++++++++++++++++++++++--- plugins/offers_invreq_hook.c | 13 ++++++ 3 files changed, 101 insertions(+), 8 deletions(-) diff --git a/common/bolt12_merkle.c b/common/bolt12_merkle.c index a745cf6e2..11de4b4e4 100644 --- a/common/bolt12_merkle.c +++ b/common/bolt12_merkle.c @@ -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); } diff --git a/plugins/fetchinvoice.c b/plugins/fetchinvoice.c index 4e1270097..62f1fc88c 100644 --- a/plugins/fetchinvoice.c +++ b/plugins/fetchinvoice.c @@ -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, diff --git a/plugins/offers_invreq_hook.c b/plugins/offers_invreq_hook.c index 177e677d2..b940bc3f9 100644 --- a/plugins/offers_invreq_hook.c +++ b/plugins/offers_invreq_hook.c @@ -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. */