Skip to content

Commit 71270ae

Browse files
committed
lightningd: make the caller set invreq_metadata and invreq_payer_id for createinvoicerequest.
It's an internal undocumented interface, which makes this change less painful. We *do* check that the invreq_metadata maps to the given invreq_payer_id, which would is required for us to sign it. Signed-off-by: Rusty Russell <[email protected]>
1 parent 74ef03d commit 71270ae

File tree

4 files changed

+66
-42
lines changed

4 files changed

+66
-42
lines changed

lightningd/offer.c

Lines changed: 26 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -348,14 +348,13 @@ static struct command_result *param_b12_invreq(struct command *cmd,
348348
if (!*invreq)
349349
return command_fail_badparam(cmd, name, buffer, tok, fail);
350350

351-
/* We use this for testing with known payer_info */
352-
if ((*invreq)->invreq_metadata && !cmd->ld->developer)
351+
if (!(*invreq)->invreq_metadata)
353352
return command_fail_badparam(cmd, name, buffer, tok,
354-
"must not have invreq_metadata");
353+
"must have invreq_metadata");
355354

356-
if ((*invreq)->invreq_payer_id)
355+
if (!(*invreq)->invreq_payer_id)
357356
return command_fail_badparam(cmd, name, buffer, tok,
358-
"must not have invreq_payer_id");
357+
"must have invreq_payer_id");
359358
return NULL;
360359
}
361360

@@ -400,15 +399,15 @@ static struct command_result *json_createinvoicerequest(struct command *cmd,
400399
struct json_stream *response;
401400
u64 *prev_basetime = NULL;
402401
struct sha256 merkle;
403-
bool *save, *single_use, *exposeid;
402+
bool *save, *single_use;
404403
enum offer_status status;
405404
struct sha256 invreq_id;
406405
const char *b12str;
406+
const u8 *tweak;
407407

408408
if (!param_check(cmd, buffer, params,
409409
p_req("bolt12", param_b12_invreq, &invreq),
410410
p_req("savetodb", param_bool, &save),
411-
p_opt_def("exposeid", param_bool, &exposeid, false),
412411
p_opt("recurrence_label", param_label, &label),
413412
p_opt_def("single_use", param_bool, &single_use, true),
414413
NULL))
@@ -419,14 +418,6 @@ static struct command_result *json_createinvoicerequest(struct command *cmd,
419418
else
420419
status = OFFER_MULTIPLE_USE_UNUSED;
421420

422-
if (!invreq->invreq_metadata)
423-
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
424-
"invoice_request has no invreq_metadata");
425-
426-
if (invreq->invreq_payer_id)
427-
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
428-
"invoice_request already has invreq_payer_id");
429-
430421
/* If it's a recurring payment, we look for previous to copy basetime */
431422
if (invreq->invreq_recurrence_counter) {
432423
if (!label)
@@ -442,27 +433,25 @@ static struct command_result *json_createinvoicerequest(struct command *cmd,
442433
}
443434
}
444435

445-
/* BOLT-offers #12:
446-
*
447-
* `invreq_metadata` might typically contain information about
448-
* the derivation of the `invreq_payer_id`. This should not
449-
* leak any information (such as using a simple BIP-32
450-
* derivation path); a valid system might be for a node to
451-
* maintain a base payer key and encode a 128-bit tweak here.
452-
* The payer_id would be derived by tweaking the base key with
453-
* SHA256(payer_base_pubkey || tweak). It's also the first
454-
* entry (if present), ensuring an unpredictable nonce for
455-
* hashing.
456-
*/
457-
invreq->invreq_payer_id = tal(invreq, struct pubkey);
458-
if (*exposeid) {
459-
*invreq->invreq_payer_id = cmd->ld->our_pubkey;
460-
} else if (!payer_key(cmd->ld,
461-
invreq->invreq_metadata,
462-
tal_bytelen(invreq->invreq_metadata),
463-
invreq->invreq_payer_id)) {
464-
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
465-
"Invalid tweak");
436+
/* If the payer_id is not our node id, we sanity check that it
437+
* correctly maps from invreq_metadata */
438+
if (!pubkey_eq(invreq->invreq_payer_id, &cmd->ld->our_pubkey)) {
439+
struct pubkey expected;
440+
if (!payer_key(cmd->ld,
441+
invreq->invreq_metadata,
442+
tal_bytelen(invreq->invreq_metadata),
443+
&expected)) {
444+
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
445+
"Invalid tweak");
446+
}
447+
if (!pubkey_eq(invreq->invreq_payer_id, &expected)) {
448+
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
449+
"payer_id did not match invreq_metadata derivation %s",
450+
fmt_pubkey(tmpctx, &expected));
451+
}
452+
tweak = invreq->invreq_metadata;
453+
} else {
454+
tweak = NULL;
466455
}
467456

468457
if (command_check_only(cmd))
@@ -477,7 +466,7 @@ static struct command_result *json_createinvoicerequest(struct command *cmd,
477466
merkle_tlv(invreq->fields, &merkle);
478467
invreq->signature = tal(invreq, struct bip340sig);
479468
hsm_sign_b12(cmd->ld, "invoice_request", "signature",
480-
&merkle, *exposeid ? NULL : invreq->invreq_metadata,
469+
&merkle, tweak,
481470
invreq->invreq_payer_id, invreq->signature);
482471

483472
b12str = invrequest_encode(cmd, invreq);

plugins/fetchinvoice.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,20 @@ static struct command_result *param_dev_reply_path(struct command *cmd, const ch
766766
return NULL;
767767
}
768768

769+
static bool payer_key(const u8 *public_tweak, size_t public_tweak_len,
770+
struct pubkey *key)
771+
{
772+
struct sha256 tweakhash;
773+
774+
bolt12_alias_tweak(&nodealias_base, public_tweak, public_tweak_len,
775+
&tweakhash);
776+
777+
*key = id;
778+
return secp256k1_ec_pubkey_tweak_add(secp256k1_ctx,
779+
&key->pubkey,
780+
tweakhash.u.u8) == 1;
781+
}
782+
769783
/* Fetches an invoice for this offer, and makes sure it corresponds. */
770784
struct command_result *json_fetchinvoice(struct command *cmd,
771785
const char *buffer,
@@ -965,6 +979,16 @@ struct command_result *json_fetchinvoice(struct command *cmd,
965979
tal_bytelen(invreq->invreq_metadata));
966980
}
967981

982+
/* We derive transient payer_id from invreq_metadata */
983+
invreq->invreq_payer_id = tal(invreq, struct pubkey);
984+
if (!payer_key(invreq->invreq_metadata,
985+
tal_bytelen(invreq->invreq_metadata),
986+
invreq->invreq_payer_id)) {
987+
/* Doesn't happen! */
988+
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
989+
"Invalid tweak for payer_id");
990+
}
991+
968992
/* BOLT-offers #12:
969993
*
970994
* - if `offer_chains` is set:

plugins/offers_offer.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -674,7 +674,8 @@ struct command_result *json_invoicerequest(struct command *cmd,
674674
*...
675675
* - MUST set `invreq_payer_id` as it would set `offer_issuer_id` for an offer.
676676
*/
677-
/* createinvoicerequest sets this! */
677+
/* FIXME: Allow invoicerequests using aliases! */
678+
invreq->invreq_payer_id = tal_dup(invreq, struct pubkey, &id);
678679

679680
/* BOLT-offers #12:
680681
* - if it supports bolt12 invoice request features:
@@ -685,8 +686,6 @@ struct command_result *json_invoicerequest(struct command *cmd,
685686
invreq);
686687
json_add_string(req->js, "bolt12", invrequest_encode(tmpctx, invreq));
687688
json_add_bool(req->js, "savetodb", true);
688-
/* FIXME: Allow invoicerequests using aliases! */
689-
json_add_bool(req->js, "exposeid", true);
690689
json_add_bool(req->js, "single_use", *single_use);
691690
if (label)
692691
json_add_string(req->js, "recurrence_label", label);

tests/test_pay.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5254,9 +5254,21 @@ def test_payerkey(node_factory):
52545254
"030b68257230f7057e694222bbd54d9d108decced6b647a90da6f578360af53f7d",
52555255
"02f402bd7374a1304b07c7236d9c683b83f81072517195ddede8ab328026d53157"]
52565256

5257+
bolt12tool = os.path.join(os.path.dirname(__file__), "..", "devtools", "bolt12-cli")
5258+
5259+
# Returns "lnr <hexstring>" on first line
5260+
hexprefix = subprocess.check_output([bolt12tool, 'decodehex',
5261+
'lnr1qqgz2d7u2smys9dc5q2447e8thjlgq3qqc3xu3s3rg94nj40zfsy866mhu5vxne6tcej5878k2mneuvgjy8ssqvepgz5zsjrg3z3vggzvkm2khkgvrxj27r96c00pwl4kveecdktm29jdd6w0uwu5jgtv5v9qgqxyfhyvyg6pdvu4tcjvpp7kkal9rp57wj7xv4pl3ajku70rzy3pu']).decode('UTF-8').split('\n')[0].split()
5262+
5263+
# Now we are supposed to put invreq_payer_id inside invreq, and lightningd
5264+
# checks the derivation as a courtesy. Fortunately, invreq_payer_id is last
52575265
for n, k in zip(nodes, expected_keys):
5258-
b12 = n.rpc.createinvoicerequest('lnr1qqgz2d7u2smys9dc5q2447e8thjlgq3qqc3xu3s3rg94nj40zfsy866mhu5vxne6tcej5878k2mneuvgjy8ssqvepgz5zsjrg3z3vggzvkm2khkgvrxj27r96c00pwl4kveecdktm29jdd6w0uwu5jgtv5v9qgqxyfhyvyg6pdvu4tcjvpp7kkal9rp57wj7xv4pl3ajku70rzy3pu', False)['bolt12']
5259-
assert n.rpc.decode(b12)['invreq_payer_id'] == k
5266+
# BOLT-offers #12:
5267+
# 1. type: 88 (`invreq_payer_id`)
5268+
# 2. data:
5269+
# * [`point`:`key`]
5270+
encoded = subprocess.check_output([bolt12tool, 'encodehex'] + hexprefix + ['5821', k]).decode('UTF-8').strip()
5271+
n.rpc.createinvoicerequest(encoded, False)['bolt12']
52605272

52615273

52625274
def test_pay_multichannel_use_zeroconf(bitcoind, node_factory):

0 commit comments

Comments
 (0)