From 595fbd2a19abe2c98ba66a34fe65fd214122bc40 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 9 Nov 2022 12:57:04 +1030 Subject: [PATCH] createinvoice: make a minimal blinded "path" in bolt12 invoice if none presented. The "path" is just a message to ourselves. This meets the minimal requirement for bolt12 invoices: that there be a blinded path (at least so we can use the path_id inside in place of "payment_secret"). We expose the method to make this path_id to a common routine: offers will need this for generating more sophisticated paths. Signed-off-by: Rusty Russell --- common/Makefile | 1 + common/invoice_path_id.c | 19 ++++++ common/invoice_path_id.h | 29 +++++++++ lightningd/Makefile | 1 + lightningd/hsm_control.c | 12 ++++ lightningd/invoice.c | 71 +++++++++++++++++++++ lightningd/lightningd.h | 3 + lightningd/test/run-invoice-select-inchan.c | 17 +++++ tests/test_pay.py | 3 +- 9 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 common/invoice_path_id.c create mode 100644 common/invoice_path_id.h diff --git a/common/Makefile b/common/Makefile index 8112120ef..273bd153c 100644 --- a/common/Makefile +++ b/common/Makefile @@ -47,6 +47,7 @@ COMMON_SRC_NOGEN := \ common/interactivetx.c \ common/initial_channel.c \ common/initial_commit_tx.c \ + common/invoice_path_id.c \ common/iso4217.c \ common/json_filter.c \ common/json_param.c \ diff --git a/common/invoice_path_id.c b/common/invoice_path_id.c new file mode 100644 index 000000000..5cccbcc50 --- /dev/null +++ b/common/invoice_path_id.c @@ -0,0 +1,19 @@ +#include "config.h" +#include +#include +#include + +u8 *invoice_path_id(const tal_t *ctx, + const struct secret *base_secret, + const struct sha256 *payment_hash) +{ + struct sha256_ctx shactx; + struct sha256 secret; + + sha256_init(&shactx); + sha256_update(&shactx, base_secret, sizeof(*base_secret)); + sha256_update(&shactx, payment_hash, sizeof(*payment_hash)); + sha256_done(&shactx, &secret); + + return (u8 *)tal_dup(ctx, struct sha256, &secret); +} diff --git a/common/invoice_path_id.h b/common/invoice_path_id.h new file mode 100644 index 000000000..4e4566de4 --- /dev/null +++ b/common/invoice_path_id.h @@ -0,0 +1,29 @@ +#ifndef LIGHTNING_COMMON_INVOICE_PATH_ID_H +#define LIGHTNING_COMMON_INVOICE_PATH_ID_H +#include "config.h" +#include +#include + +struct secret; +struct sha256; + +/* String to use with makesecret to get the invoice base secret */ +#define INVOICE_PATH_BASE_STRING "bolt12-invoice-base" + +/** + * invoice_path_id: generate the "path_id" field for the tlv_encrypted_data_tlv + * @ctx: tal context + * @payment_hash: the invoice payment hash + * @base_secret: the node-specific secret makesecret("bolt12-invoice-base") + * + * Receiving a blinded, encrypted tlv_encrypted_data_tlv containing + * the correct path_id is how we know this blinded path is the correct + * one for this invoice payment. + * + * It's exposed here as plugins may want to generate blinded paths. + */ +u8 *invoice_path_id(const tal_t *ctx, + const struct secret *base_secret, + const struct sha256 *payment_hash); + +#endif /* LIGHTNING_COMMON_INVOICE_PATH_ID_H */ diff --git a/lightningd/Makefile b/lightningd/Makefile index 8d2d32603..0e2418be1 100644 --- a/lightningd/Makefile +++ b/lightningd/Makefile @@ -99,6 +99,7 @@ LIGHTNINGD_COMMON_OBJS := \ common/htlc_state.o \ common/htlc_trim.o \ common/htlc_wire.o \ + common/invoice_path_id.o \ common/key_derive.o \ common/keyset.o \ common/json_filter.o \ diff --git a/lightningd/hsm_control.c b/lightningd/hsm_control.c index da849e0a4..0cd4be1cc 100644 --- a/lightningd/hsm_control.c +++ b/lightningd/hsm_control.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -140,6 +141,17 @@ struct ext_key *hsm_init(struct lightningd *ld) "HSM gave invalid v1 bolt12_base"); } + /* This is equivalent to makesecret("bolt12-invoice-base") */ + msg = towire_hsmd_derive_secret(NULL, tal_dup_arr(tmpctx, u8, + (const u8 *)INVOICE_PATH_BASE_STRING, + strlen(INVOICE_PATH_BASE_STRING), 0)); + if (!wire_sync_write(ld->hsm_fd, take(msg))) + err(EXITCODE_HSM_GENERIC_ERROR, "Writing derive_secret msg to hsm"); + + msg = wire_sync_read(tmpctx, ld->hsm_fd); + if (!fromwire_hsmd_derive_secret_reply(msg, &ld->invoicesecret_base)) + err(EXITCODE_HSM_GENERIC_ERROR, "Bad derive_secret_reply"); + return bip32_base; } diff --git a/lightningd/invoice.c b/lightningd/invoice.c index b8e0dcbe9..19df99ee8 100644 --- a/lightningd/invoice.c +++ b/lightningd/invoice.c @@ -5,9 +5,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -1641,6 +1643,69 @@ static struct command_result *fail_exists(struct command *cmd, return command_failed(cmd, data); } +/* This is only if we're a public node; otherwise, the offers plugin + * will have populated a real blinded path */ +static struct tlv_invoice *add_stub_blindedpath(const tal_t *ctx, + struct lightningd *ld, + struct tlv_invoice *inv STEALS) +{ + struct blinded_path *path; + struct privkey blinding; + struct tlv_encrypted_data_tlv *tlv; + u8 *wire; + size_t dlen; + + path = tal(NULL, struct blinded_path); + if (!pubkey_from_node_id(&path->first_node_id, &ld->id)) + abort(); + randombytes_buf(&blinding, sizeof(blinding)); + if (!pubkey_from_privkey(&blinding, &path->blinding)) + abort(); + path->path = tal_arr(path, struct onionmsg_hop *, 1); + path->path[0] = tal(path->path, struct onionmsg_hop); + + /* A message in a bottle to ourselves: match it with + * the invoice: we assume the payment_hash is unique! */ + tlv = tlv_encrypted_data_tlv_new(tmpctx); + tlv->path_id = invoice_path_id(inv, + &ld->invoicesecret_base, + inv->payment_hash); + + path->path[0]->encrypted_recipient_data + = encrypt_tlv_encrypted_data(path->path[0], + &blinding, + &path->first_node_id, + tlv, + NULL, + &path->path[0]->blinded_node_id); + + inv->paths = tal_arr(inv, struct blinded_path *, 1); + inv->paths[0] = tal_steal(inv->paths, path); + + /* BOLT-offers #12: + * - MUST include `invoice_paths` containing one or more paths to the node. + * - MUST specify `invoice_paths` in order of most-preferred to least-preferred if it has a preference. + * - MUST include `invoice_blindedpay` with exactly one `blinded_payinfo` for each `blinded_path` in `paths`, in order. + */ + inv->blindedpay = tal_arr(inv, struct blinded_payinfo *, 1); + inv->blindedpay[0] = tal(inv->blindedpay, struct blinded_payinfo); + inv->blindedpay[0]->fee_base_msat = 0; + inv->blindedpay[0]->fee_proportional_millionths = 0; + inv->blindedpay[0]->cltv_expiry_delta = ld->config.cltv_final; + inv->blindedpay[0]->htlc_minimum_msat = AMOUNT_MSAT(0); + inv->blindedpay[0]->htlc_maximum_msat = AMOUNT_MSAT(21000000 * MSAT_PER_BTC); + inv->blindedpay[0]->features = NULL; + + /* But we need to update ->fields, so re-linearize */ + wire = tal_arr(tmpctx, u8, 0); + towire_tlv_invoice(&wire, inv); + tal_free(inv); + + dlen = tal_bytelen(wire); + return fromwire_tlv_invoice(ctx, + cast_const2(const u8 **, &wire), &dlen); +} + static struct command_result *json_createinvoice(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, @@ -1717,6 +1782,12 @@ static struct command_result *json_createinvoice(struct command *cmd, "Unparsable invoice '%s': %s", invstring, fail); + /* If they don't create a blinded path, add a simple one so we + * can recognize payments (bolt12 doesn't use + * payment_secret) */ + if (!inv->paths) + inv = add_stub_blindedpath(cmd, cmd->ld, inv); + if (inv->signature) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "invoice already signed"); diff --git a/lightningd/lightningd.h b/lightningd/lightningd.h index 8ada62912..029b9038f 100644 --- a/lightningd/lightningd.h +++ b/lightningd/lightningd.h @@ -124,6 +124,9 @@ struct lightningd { /* The public base for our payer_id keys */ struct pubkey bolt12_base; + /* Secret base for our invoices */ + struct secret invoicesecret_base; + /* Feature set we offer. */ struct feature_set *our_features; diff --git a/lightningd/test/run-invoice-select-inchan.c b/lightningd/test/run-invoice-select-inchan.c index 95b27e1c3..0191cecf5 100644 --- a/lightningd/test/run-invoice-select-inchan.c +++ b/lightningd/test/run-invoice-select-inchan.c @@ -182,6 +182,15 @@ char *encode_scriptpubkey_to_addr(const tal_t *ctx UNNEEDED, const struct chainparams *chainparams UNNEEDED, const u8 *scriptPubkey UNNEEDED) { fprintf(stderr, "encode_scriptpubkey_to_addr called!\n"); abort(); } +/* Generated stub for encrypt_tlv_encrypted_data */ +u8 *encrypt_tlv_encrypted_data(const tal_t *ctx UNNEEDED, + const struct privkey *blinding UNNEEDED, + const struct pubkey *node UNNEEDED, + const struct tlv_encrypted_data_tlv *tlv UNNEEDED, + struct privkey *next_blinding UNNEEDED, + struct pubkey *node_alias) + +{ fprintf(stderr, "encrypt_tlv_encrypted_data called!\n"); abort(); } /* Generated stub for failmsg_incorrect_or_unknown_ */ const u8 *failmsg_incorrect_or_unknown_(const tal_t *ctx UNNEEDED, struct lightningd *ld UNNEEDED, @@ -306,6 +315,11 @@ struct tlv_invoice *invoice_decode_nosig(const tal_t *ctx UNNEEDED, /* Generated stub for invoice_encode */ char *invoice_encode(const tal_t *ctx UNNEEDED, const struct tlv_invoice *bolt12_tlv UNNEEDED) { fprintf(stderr, "invoice_encode called!\n"); abort(); } +/* Generated stub for invoice_path_id */ +u8 *invoice_path_id(const tal_t *ctx UNNEEDED, + const struct secret *base_secret UNNEEDED, + const struct sha256 *payment_hash UNNEEDED) +{ fprintf(stderr, "invoice_path_id called!\n"); abort(); } /* Generated stub for json_add_address */ void json_add_address(struct json_stream *response UNNEEDED, const char *fieldname UNNEEDED, const struct wireaddr *addr UNNEEDED) @@ -687,6 +701,9 @@ bool plugin_hook_call_(struct lightningd *ld UNNEEDED, void plugin_request_send(struct plugin *plugin UNNEEDED, struct jsonrpc_request *req TAKES UNNEEDED) { fprintf(stderr, "plugin_request_send called!\n"); abort(); } +/* Generated stub for pubkey_from_node_id */ +bool pubkey_from_node_id(struct pubkey *key UNNEEDED, const struct node_id *id UNNEEDED) +{ fprintf(stderr, "pubkey_from_node_id called!\n"); abort(); } /* Generated stub for report_subd_memleak */ void report_subd_memleak(struct leak_detect *leak_detect UNNEEDED, struct subd *leaker UNNEEDED) { fprintf(stderr, "report_subd_memleak called!\n"); abort(); } diff --git a/tests/test_pay.py b/tests/test_pay.py index db8cc5ac0..3ed145d57 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -4665,7 +4665,8 @@ def test_fetchinvoice(node_factory, bitcoind): l1.rpc.pay(inv1['invoice']) # We can't pay the other one now. - with pytest.raises(RpcError, match="INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS.*'erring_node': '{}'".format(l3.info['id'])): + # FIXME: Even dummy blinded paths always return WIRE_INVALID_ONION_BLINDING! + with pytest.raises(RpcError, match="INVALID_ONION_BLINDING.*'erring_node': '{}'".format(l3.info['id'])): l1.rpc.pay(inv2['invoice']) # We can't reuse the offer, either.