Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .msggen.json
Original file line number Diff line number Diff line change
Expand Up @@ -3322,6 +3322,7 @@
"Offer.absolute_expiry": 6,
"Offer.amount": 1,
"Offer.description": 2,
"Offer.force_issuer_id": 15,
"Offer.issuer": 3,
"Offer.label": 4,
"Offer.optional_recurrence": 14,
Expand Down Expand Up @@ -11924,6 +11925,10 @@
"added": "pre-v0.10.1",
"deprecated": null
},
"Offer.force_issuer_id": {
"added": "v26.03",
"deprecated": null
},
"Offer.issuer": {
"added": "pre-v0.10.1",
"deprecated": null
Expand Down
1 change: 1 addition & 0 deletions cln-grpc/proto/node.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions cln-grpc/src/convert.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions cln-rpc/src/model.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

148 changes: 148 additions & 0 deletions common/blindedpath.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "config.h"
#include <string.h>
#include <bitcoin/tx.h>
#include <ccan/endian/endian.h>
#include <common/blindedpath.h>
#include <common/blinding.h>
#include <common/bolt11.h>
Expand All @@ -13,6 +15,33 @@
#define SUPERVERBOSE(...)
#endif

/*
* The `first_path_privkey` (`e_0`) is derived using
* `e_0 = HMAC256(\text{"first_path_privkey"}, SHA256(path_id || N_0 || path_index))`,
* where `path_id` is from the offer's `encrypted_data_tlv` that is only known by the
* payee, where `N_0` is the `first_node_id` of the path and where `path_index`
* is a big-endian 64-bit integer containing the path's index, to
* differentiate potential multiple paths using the same `first_node_id`.
* The deterministic generation of e_0 allows for the recovery of the offer paths.
*/
void derive_first_path_privkey(const struct secret *path_id, const struct pubkey *first_node, const size_t path_index, struct privkey *first_path_privkey)
{
u8 der[PUBKEY_CMPR_LEN];
struct sha256_ctx shactx;
struct secret sha;
uint64_t wire_path_index = cpu_to_be64(path_index);

pubkey_to_der(der, first_node);
sha256_init(&shactx);
sha256_update(&shactx, path_id->data, sizeof(path_id->data));
sha256_update(&shactx, der, sizeof(der));

sha256_update(&shactx, (u8*)&wire_path_index, sizeof(wire_path_index));
assert(sizeof(sha.data) == sizeof(struct sha256));
sha256_done(&shactx, (struct sha256*)sha.data);
subkey_from_hmac("first_path_privkey", &sha, &first_path_privkey->secret);
}

/* Blinds node_id and calculates next blinding factor. */
static bool blind_node(const struct privkey *path_privkey,
const struct secret *ss,
Expand Down Expand Up @@ -256,3 +285,122 @@ void blindedpath_next_path_key(const struct tlv_encrypted_data_tlv *enc,
blinding_next_path_key(path_key, &h, next_path_key);
}
}

/*
* The blinded path node ids and the path_pubkey of the last used hop are
* calculated given a set of blinded paths, the used blinded_node_id and the
* path_id (secret). The index of the used path is returned, unless an error
* occurred, in which case a negative value is returned.
*/
ssize_t unblind_paths(const tal_t *ctx,
struct blinded_path * const * const paths,
struct pubkey const * const blinded_node_id,
struct secret const * const path_id,
struct pubkey *** const node_ids,
struct pubkey ** const path_pubkey
)
{
struct privkey path_privkey, next_path_privkey;
struct secret ss;
struct pubkey node_alias;
struct pubkey* last_node_id = NULL;
struct tlv_encrypted_data_tlv *encmsg;
size_t nhops;
ssize_t i, j;
ssize_t used_index = -1;
const size_t npaths = tal_count(paths);

if (npaths == 0) return -1;

*node_ids = tal_arr(ctx, struct pubkey*, npaths);

/* Loop over all blinded_paths */
for (i = 0; i < npaths; ++i) {
nhops = tal_count(paths[i]->path);

/* There must be at least one hop */
if (nhops == 0) return -1;

/* Generated offers by us are assumed to only use pubkeys as
* first_node_id (Maybe not always true when using dev_paths?)*/
if (!paths[i]->first_node_id.is_pubkey) return -1;
(*node_ids)[i] = tal_arrz(ctx, struct pubkey, nhops);

(*node_ids)[i][0] = paths[i]->first_node_id.pubkey;
derive_first_path_privkey(path_id,
(*node_ids)[i],
i, &path_privkey);

for (j = 0;; ++j) {

if (!paths[i]->path[j]->encrypted_recipient_data)
return -1;

/* BOLT #4:
* - $`ss_i = SHA256(e_i * N_i) = SHA256(k_i * E_i)`$
* (ECDH shared secret known only by $`N_r`$ and $`N_i`$)
*/
if (secp256k1_ecdh(secp256k1_ctx, ss.data,
&(*node_ids)[i][j].pubkey, path_privkey.secret.data,
NULL, NULL) != 1)
return -1;
SUPERVERBOSE("\t\"ss\": \"%s\",\n",
fmt_secret(tmpctx, &ss));

/* This calculates the node's alias, and next path_key */
if (!blind_node(&path_privkey, &ss, (*node_ids)[i]+j,
&node_alias, &next_path_privkey))
return -1;

/* Verify that the blinded node id calculated by
* tweaking the node id using path_privkey matches the
* one provided in the path
* */
if (pubkey_cmp(&paths[i]->path[j]->blinded_node_id, &node_alias)) return -1;
encmsg = decrypt_encrypted_data(ctx, &ss, paths[i]->path[j]->encrypted_recipient_data);

if (!encmsg)
return -1;

if (!encmsg->next_node_id) {

if (j != nhops - 1)
return -1;

if (!encmsg->path_id || tal_count(encmsg->path_id) != sizeof(path_id->data))
return -1;

/* Verify that we generated the path by
* verifying the stored secret
*/
if (memcmp(encmsg->path_id, path_id->data, sizeof(path_id->data)))
return -1;

if (last_node_id) {

/* Verify that the last hop of each blinded path points to the
* same node id */
if (pubkey_cmp((*node_ids)[i] + j, last_node_id))
return -1;
} else last_node_id = (*node_ids)[i] + j;

/* Check if this is the path that contains blinded_node_id */
if (!pubkey_cmp(&paths[i]->path[j]->blinded_node_id, blinded_node_id)) {
used_index = i;
*path_pubkey = tal(ctx, struct pubkey);

if (!pubkey_from_privkey(&path_privkey, *path_pubkey))
return -1;
}
/* Only the last hop has next_node_id unset */
break;
}

/* The last hop cannot have next_node_id set */
if (j == nhops - 1) return -1;
(*node_ids)[i][j+1] = *encmsg->next_node_id;
path_privkey = next_path_privkey;
}
}
return used_index;
}
11 changes: 11 additions & 0 deletions common/blindedpath.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "config.h"
#include <ccan/short_types/short_types.h>
#include <ccan/tal/tal.h>
#include <wire/onion_wiregen.h>

struct route_info;
struct pubkey;
Expand All @@ -13,6 +14,8 @@ struct tlv_encrypted_data_tlv;
struct tlv_encrypted_data_tlv_payment_constraints;
struct tlv_encrypted_data_tlv_payment_relay;

void derive_first_path_privkey(const struct secret *path_id, const struct pubkey *first_node, const size_t path_index, struct privkey *first_path_privkey);

/**
* encrypt_tlv_encrypted_data - Encrypt a tlv_encrypted_data_tlv.
* @ctx: tal context
Expand Down Expand Up @@ -86,4 +89,12 @@ void blindedpath_next_path_key(const struct tlv_encrypted_data_tlv *enc,
const struct secret *ss,
struct pubkey *next_path_key);

ssize_t unblind_paths(const tal_t *ctx,
struct blinded_path * const * const paths,
struct pubkey const * const blinded_node_id,
struct secret const * const path_id,
struct pubkey *** const node_ids,
struct pubkey ** const path_pubkey
);

#endif /* LIGHTNING_COMMON_BLINDEDPATH_H */
3 changes: 2 additions & 1 deletion common/hsm_version.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
* v6 with hsm_secret struct cleanup: 06c56396fe42f4f47911d7f865dd0004d264fc1348f89547743755b6b33fec90
* v6 with hsm_secret_type TLV: 7bb5deb2367482feb084d304ee14b2373d42910ad56484fbf47614dbb3d4cb74
* v6 with bip86_base in TLV: 6bb6e6ee256f22a6fb41856c90feebde3065a9074e79a46731e453a932be83f0
* v7 with sign_bolt12 using path_pubkey for invoices: 53792d2d257dd1b1b29d5945903c8d11190b82d1ff27d44d9ac155d06851de5c
*/
#define HSM_MIN_VERSION 5
#define HSM_MAX_VERSION 6
#define HSM_MAX_VERSION 7
#endif /* LIGHTNING_COMMON_HSM_VERSION_H */
13 changes: 12 additions & 1 deletion common/onion_message.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,11 @@ get_nodeid(const struct tlv_encrypted_data_tlv **tlvs,
}

/* Stage 1: tlv_encrypted_data_tlv[] -> struct blinded_path.
* A non-negative path_index indicates that `e_0` should be generated
* deterministically and provides the blinded path's index.
* Optional array of node_ids, consulted iff tlv uses scid in one entry. */
struct blinded_path *blinded_path_from_encdata_tlvs(const tal_t *ctx,
const ssize_t path_index,
const struct tlv_encrypted_data_tlv **tlvs,
const struct pubkey *ids)
{
Expand All @@ -67,7 +70,12 @@ struct blinded_path *blinded_path_from_encdata_tlvs(const tal_t *ctx,
assert(nhops > 0);
assert(tal_count(ids) > 0);

randbytes(&first_blinding, sizeof(first_blinding));
if (path_index >= 0) {
assert(tlvs[nhops-1]->path_id && tal_bytelen(tlvs[nhops-1]->path_id) == sizeof(struct secret));
derive_first_path_privkey((struct secret const*)tlvs[nhops-1]->path_id, ids, path_index, &first_blinding);

} else randbytes(&first_blinding, sizeof(first_blinding));

if (!pubkey_from_privkey(&first_blinding, &path->first_path_key))
abort();
sciddir_or_pubkey_from_pubkey(&path->first_node_id, &ids[0]);
Expand Down Expand Up @@ -132,6 +140,7 @@ struct sphinx_hop **onionmsg_tlvs_to_hops(const tal_t *ctx,
}

struct blinded_path *incoming_message_blinded_path(const tal_t *ctx,
const ssize_t path_index,
const struct pubkey *ids,
const struct short_channel_id **scids,
const struct secret *path_secret)
Expand All @@ -148,6 +157,7 @@ struct blinded_path *incoming_message_blinded_path(const tal_t *ctx,
ARRAY_SIZE(path_secret->data), 0);

return blinded_path_from_encdata_tlvs(ctx,
path_index,
cast_const2(const struct tlv_encrypted_data_tlv **, etlvs),
ids);
}
Expand Down Expand Up @@ -196,6 +206,7 @@ struct onion_message *outgoing_onion_message(const tal_t *ctx,
}

our_path = blinded_path_from_encdata_tlvs(tmpctx,
-1,
cast_const2(const struct tlv_encrypted_data_tlv **, etlvs),
ids);

Expand Down
2 changes: 2 additions & 0 deletions common/onion_message.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ struct tlv_encrypted_data_tlv **new_encdata_tlvs(const tal_t *ctx,
* You can turn the first_node_id into an scidd after if you want to.
*/
struct blinded_path *blinded_path_from_encdata_tlvs(const tal_t *ctx,
const ssize_t path_index,
const struct tlv_encrypted_data_tlv **tlvs,
const struct pubkey *ids);

Expand Down Expand Up @@ -98,6 +99,7 @@ struct sphinx_hop **onionmsg_tlvs_to_hops(const tal_t *ctx,
* @path_secret: put this into final entry, so we can verify.
*/
struct blinded_path *incoming_message_blinded_path(const tal_t *ctx,
const ssize_t path_index,
const struct pubkey *ids,
const struct short_channel_id **scids,
const struct secret *path_secret);
Expand Down
1 change: 1 addition & 0 deletions connectd/connectd_wire.csv
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ msgdata,connectd_got_onionmsg_to_us,path_secret,?secret,
msgdata,connectd_got_onionmsg_to_us,reply,?blinded_path,
msgdata,connectd_got_onionmsg_to_us,rawmsg_len,u16,
msgdata,connectd_got_onionmsg_to_us,rawmsg,u8,rawmsg_len
msgdata,connectd_got_onionmsg_to_us,final_alias,pubkey,

# Lightningd tells us to send an onion message.
msgtype,connectd_send_onionmsg,2041
Expand Down
2 changes: 1 addition & 1 deletion connectd/onion_message.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ static const char *handle_onion(const tal_t *ctx,
take(towire_connectd_got_onionmsg_to_us(NULL,
final_path_id,
final_om->reply_path,
omsg)));
omsg, &final_alias)));
} else {
struct node_id next_node_id;
struct peer *next_peer;
Expand Down
7 changes: 7 additions & 0 deletions contrib/msggen/msggen/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -28083,6 +28083,13 @@
"Make recurrence optional, for backwards compatibility (older payers will only pay once)."
]
},
"force_issuer_id": {
"added": "v26.03",
"type": "boolean",
"description": [
"Force the inclusion of `offer_issuer_id` even when not required due to the presence of blinded paths."
]
},
"dev_paths": {
"hidden": true
}
Expand Down
3 changes: 2 additions & 1 deletion contrib/pyln-client/pyln/client/lightning.py
Original file line number Diff line number Diff line change
Expand Up @@ -1130,7 +1130,7 @@ def newaddr(self, addresstype=None):

def offer(self, amount, description=None, issuer=None, label=None, quantity_max=None, absolute_expiry=None,
recurrence=None, recurrence_base=None, recurrence_paywindow=None, recurrence_limit=None,
single_use=None):
single_use=None, force_issuer_id=None):
"""
Create an offer (or returns an existing one), which is a precursor to creating one or more invoices.
It automatically enables the processing of an incoming invoice_request, and issuing of invoices.
Expand All @@ -1147,6 +1147,7 @@ def offer(self, amount, description=None, issuer=None, label=None, quantity_max=
"recurrence_paywindow": recurrence_paywindow,
"recurrence_limit": recurrence_limit,
"single_use": single_use,
"force_issuer_id": force_issuer_id,
}
return self.call("offer", payload)

Expand Down
1,200 changes: 600 additions & 600 deletions contrib/pyln-grpc-proto/pyln/grpc/node_pb2.py

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions doc/schemas/offer.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@
"Make recurrence optional, for backwards compatibility (older payers will only pay once)."
]
},
"force_issuer_id": {
"added": "v26.03",
"type": "boolean",
"description": [
"Force the inclusion of `offer_issuer_id` even when not required due to the presence of blinded paths."
]
},
"dev_paths": {
"hidden": true
}
Expand Down
4 changes: 1 addition & 3 deletions hsmd/hsmd_wire.csv
Original file line number Diff line number Diff line change
Expand Up @@ -403,9 +403,7 @@ msgtype,hsmd_sign_bolt12,25
msgdata,hsmd_sign_bolt12,messagename,wirestring,
msgdata,hsmd_sign_bolt12,fieldname,wirestring,
msgdata,hsmd_sign_bolt12,merkleroot,sha256,
# This is for invreq payer_id (temporary keys)
msgdata,hsmd_sign_bolt12,publictweaklen,u16,
msgdata,hsmd_sign_bolt12,publictweak,u8,publictweaklen
msgdata,hsmd_sign_bolt12,path_pubkey,?pubkey,

#include <bitcoin/signature.h>
msgtype,hsmd_sign_bolt12_reply,125
Expand Down
Loading