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 <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell
2022-11-09 12:57:04 +10:30
committed by Christian Decker
parent a5471a405b
commit 595fbd2a19
9 changed files with 155 additions and 1 deletions

View File

@@ -47,6 +47,7 @@ COMMON_SRC_NOGEN := \
common/interactivetx.c \ common/interactivetx.c \
common/initial_channel.c \ common/initial_channel.c \
common/initial_commit_tx.c \ common/initial_commit_tx.c \
common/invoice_path_id.c \
common/iso4217.c \ common/iso4217.c \
common/json_filter.c \ common/json_filter.c \
common/json_param.c \ common/json_param.c \

19
common/invoice_path_id.c Normal file
View File

@@ -0,0 +1,19 @@
#include "config.h"
#include <bitcoin/privkey.h>
#include <ccan/crypto/sha256/sha256.h>
#include <common/invoice_path_id.h>
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);
}

29
common/invoice_path_id.h Normal file
View File

@@ -0,0 +1,29 @@
#ifndef LIGHTNING_COMMON_INVOICE_PATH_ID_H
#define LIGHTNING_COMMON_INVOICE_PATH_ID_H
#include "config.h"
#include <ccan/short_types/short_types.h>
#include <ccan/tal/tal.h>
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 */

View File

@@ -99,6 +99,7 @@ LIGHTNINGD_COMMON_OBJS := \
common/htlc_state.o \ common/htlc_state.o \
common/htlc_trim.o \ common/htlc_trim.o \
common/htlc_wire.o \ common/htlc_wire.o \
common/invoice_path_id.o \
common/key_derive.o \ common/key_derive.o \
common/keyset.o \ common/keyset.o \
common/json_filter.o \ common/json_filter.o \

View File

@@ -5,6 +5,7 @@
#include <common/errcode.h> #include <common/errcode.h>
#include <common/hsm_encryption.h> #include <common/hsm_encryption.h>
#include <common/hsm_version.h> #include <common/hsm_version.h>
#include <common/invoice_path_id.h>
#include <common/json_command.h> #include <common/json_command.h>
#include <common/json_param.h> #include <common/json_param.h>
#include <common/jsonrpc_errors.h> #include <common/jsonrpc_errors.h>
@@ -140,6 +141,17 @@ struct ext_key *hsm_init(struct lightningd *ld)
"HSM gave invalid v1 bolt12_base"); "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; return bip32_base;
} }

View File

@@ -5,9 +5,11 @@
#include <ccan/json_escape/json_escape.h> #include <ccan/json_escape/json_escape.h>
#include <ccan/str/hex/hex.h> #include <ccan/str/hex/hex.h>
#include <ccan/tal/str/str.h> #include <ccan/tal/str/str.h>
#include <common/blindedpath.h>
#include <common/bolt11_json.h> #include <common/bolt11_json.h>
#include <common/bolt12_merkle.h> #include <common/bolt12_merkle.h>
#include <common/configdir.h> #include <common/configdir.h>
#include <common/invoice_path_id.h>
#include <common/json_command.h> #include <common/json_command.h>
#include <common/json_param.h> #include <common/json_param.h>
#include <common/overflows.h> #include <common/overflows.h>
@@ -1641,6 +1643,69 @@ static struct command_result *fail_exists(struct command *cmd,
return command_failed(cmd, data); 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, static struct command_result *json_createinvoice(struct command *cmd,
const char *buffer, const char *buffer,
const jsmntok_t *obj UNNEEDED, const jsmntok_t *obj UNNEEDED,
@@ -1717,6 +1782,12 @@ static struct command_result *json_createinvoice(struct command *cmd,
"Unparsable invoice '%s': %s", "Unparsable invoice '%s': %s",
invstring, fail); 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) if (inv->signature)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"invoice already signed"); "invoice already signed");

View File

@@ -124,6 +124,9 @@ struct lightningd {
/* The public base for our payer_id keys */ /* The public base for our payer_id keys */
struct pubkey bolt12_base; struct pubkey bolt12_base;
/* Secret base for our invoices */
struct secret invoicesecret_base;
/* Feature set we offer. */ /* Feature set we offer. */
struct feature_set *our_features; struct feature_set *our_features;

View File

@@ -182,6 +182,15 @@ char *encode_scriptpubkey_to_addr(const tal_t *ctx UNNEEDED,
const struct chainparams *chainparams UNNEEDED, const struct chainparams *chainparams UNNEEDED,
const u8 *scriptPubkey UNNEEDED) const u8 *scriptPubkey UNNEEDED)
{ fprintf(stderr, "encode_scriptpubkey_to_addr called!\n"); abort(); } { 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_ */ /* Generated stub for failmsg_incorrect_or_unknown_ */
const u8 *failmsg_incorrect_or_unknown_(const tal_t *ctx UNNEEDED, const u8 *failmsg_incorrect_or_unknown_(const tal_t *ctx UNNEEDED,
struct lightningd *ld 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 */ /* Generated stub for invoice_encode */
char *invoice_encode(const tal_t *ctx UNNEEDED, const struct tlv_invoice *bolt12_tlv UNNEEDED) char *invoice_encode(const tal_t *ctx UNNEEDED, const struct tlv_invoice *bolt12_tlv UNNEEDED)
{ fprintf(stderr, "invoice_encode called!\n"); abort(); } { 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 */ /* Generated stub for json_add_address */
void json_add_address(struct json_stream *response UNNEEDED, const char *fieldname UNNEEDED, void json_add_address(struct json_stream *response UNNEEDED, const char *fieldname UNNEEDED,
const struct wireaddr *addr 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, void plugin_request_send(struct plugin *plugin UNNEEDED,
struct jsonrpc_request *req TAKES UNNEEDED) struct jsonrpc_request *req TAKES UNNEEDED)
{ fprintf(stderr, "plugin_request_send called!\n"); abort(); } { 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 */ /* Generated stub for report_subd_memleak */
void report_subd_memleak(struct leak_detect *leak_detect UNNEEDED, struct subd *leaker UNNEEDED) void report_subd_memleak(struct leak_detect *leak_detect UNNEEDED, struct subd *leaker UNNEEDED)
{ fprintf(stderr, "report_subd_memleak called!\n"); abort(); } { fprintf(stderr, "report_subd_memleak called!\n"); abort(); }

View File

@@ -4665,7 +4665,8 @@ def test_fetchinvoice(node_factory, bitcoind):
l1.rpc.pay(inv1['invoice']) l1.rpc.pay(inv1['invoice'])
# We can't pay the other one now. # 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']) l1.rpc.pay(inv2['invoice'])
# We can't reuse the offer, either. # We can't reuse the offer, either.