Skip to content

Commit 2d84be6

Browse files
committed
-Fixing the signature of invoices without an offer issuer (i.e. using a
blinded node id for signing).
1 parent ab802a6 commit 2d84be6

File tree

11 files changed

+94
-38
lines changed

11 files changed

+94
-38
lines changed

connectd/connectd_wire.csv

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ msgdata,connectd_got_onionmsg_to_us,path_secret,?secret,
148148
msgdata,connectd_got_onionmsg_to_us,reply,?blinded_path,
149149
msgdata,connectd_got_onionmsg_to_us,rawmsg_len,u16,
150150
msgdata,connectd_got_onionmsg_to_us,rawmsg,u8,rawmsg_len
151+
msgdata,connectd_got_onionmsg_to_us,final_alias,pubkey,
152+
msgdata,connectd_got_onionmsg_to_us,path_pubkey,pubkey,
151153

152154
# Lightningd tells us to send an onion message.
153155
msgtype,connectd_send_onionmsg,2041

connectd/onion_message.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ static const char *handle_onion(const tal_t *ctx,
7777
take(towire_connectd_got_onionmsg_to_us(NULL,
7878
final_path_id,
7979
final_om->reply_path,
80-
omsg)));
80+
omsg, &final_alias, path_key)));
8181
} else {
8282
struct node_id next_node_id;
8383
struct peer *next_peer;

hsmd/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ HSMD_COMMON_OBJS := \
3535
common/daemon_conn.o \
3636
common/derive_basepoints.o \
3737
common/hash_u5.o \
38+
common/hmac.o \
3839
common/hsm_encryption.o \
3940
common/htlc_wire.o \
4041
common/key_derive.o \

hsmd/hsmd_wire.csv

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -388,9 +388,7 @@ msgtype,hsmd_sign_bolt12,25
388388
msgdata,hsmd_sign_bolt12,messagename,wirestring,
389389
msgdata,hsmd_sign_bolt12,fieldname,wirestring,
390390
msgdata,hsmd_sign_bolt12,merkleroot,sha256,
391-
# This is for invreq payer_id (temporary keys)
392-
msgdata,hsmd_sign_bolt12,publictweaklen,u16,
393-
msgdata,hsmd_sign_bolt12,publictweak,u8,publictweaklen
391+
msgdata,hsmd_sign_bolt12,path_pubkey,?pubkey,
394392

395393
msgtype,hsmd_sign_bolt12_reply,125
396394
msgdata,hsmd_sign_bolt12_reply,sig,bip340sig,

hsmd/libhsmd.c

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,7 @@ static u8 *handle_sign_option_will_fund_offer(struct hsmd_client *c,
780780
return towire_hsmd_sign_option_will_fund_offer_reply(NULL, &sig);
781781
}
782782

783+
/*
783784
static void payer_key_tweak(const struct pubkey *bolt12,
784785
const u8 *publictweak, size_t publictweaklen,
785786
struct sha256 *tweak)
@@ -794,48 +795,63 @@ static void payer_key_tweak(const struct pubkey *bolt12,
794795
sha256_update(&sha, publictweak, publictweaklen);
795796
sha256_done(&sha, tweak);
796797
}
798+
*/
797799

798-
/*~ lightningd asks us to sign a bolt12 (e.g. offer). */
800+
static void node_blinded_privkey(const struct pubkey *path_pubkey, struct privkey *blinded_privkey)
801+
{
802+
struct secret ss;
803+
struct secret node_id_blinding;
804+
805+
node_key(blinded_privkey, NULL);
806+
807+
/* BOLT #4:
808+
* - $`ss_i = SHA256(e_i * N_i) = SHA256(k_i * E_i)`$
809+
* (ECDH shared secret known only by $`N_r`$ and $`N_i`$)
810+
*/
811+
if (secp256k1_ecdh(secp256k1_ctx, ss.data,
812+
&path_pubkey->pubkey, blinded_privkey->secret.data,
813+
NULL, NULL) != 1)
814+
hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR,
815+
"Could not compute ss from path_key.");
816+
817+
/* BOLT #4:
818+
* - $`B_i = HMAC256(\text{"blinded\_node\_id"}, ss_i) * N_i`$
819+
* (blinded `node_id` for $`N_i`$, private key known only by $`N_i`$)
820+
*/
821+
subkey_from_hmac("blinded_node_id", &ss, &node_id_blinding);
822+
823+
if (secp256k1_ec_seckey_tweak_mul(secp256k1_ctx,
824+
blinded_privkey->secret.data,
825+
node_id_blinding.data) != 1)
826+
hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR,
827+
"Could tweak bolt12 key.");
828+
}
829+
830+
/*~ lightningd asks us to sign a bolt12 invoice. */
799831
static u8 *handle_sign_bolt12(struct hsmd_client *c, const u8 *msg_in)
800832
{
801833
char *messagename, *fieldname;
802834
struct sha256 merkle, sha;
803835
struct bip340sig sig;
804836
secp256k1_keypair kp;
805-
u8 *publictweak;
837+
struct pubkey* path_pubkey;
806838

807839
if (!fromwire_hsmd_sign_bolt12(tmpctx, msg_in,
808840
&messagename, &fieldname, &merkle,
809-
&publictweak))
841+
&path_pubkey))
810842
return hsmd_status_malformed_request(c, msg_in);
811843

812844
sighash_from_merkle(messagename, fieldname, &merkle, &sha);
813845

814-
if (!publictweak) {
846+
if (!path_pubkey) {
815847
node_schnorrkey(&kp);
816848
} else {
817-
/* If we're tweaking key, we use bolt12 key */
818-
struct privkey tweakedkey;
819-
struct pubkey bolt12;
820-
struct sha256 tweak;
849+
struct privkey blinded_privkey;
821850

822-
if (secp256k1_ec_pubkey_create(secp256k1_ctx, &bolt12.pubkey,
823-
secretstuff.bolt12.data) != 1)
824-
hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR,
825-
"Could derive bolt12 public key.");
826-
827-
payer_key_tweak(&bolt12, publictweak, tal_bytelen(publictweak),
828-
&tweak);
829-
830-
tweakedkey.secret = secretstuff.bolt12;
831-
if (secp256k1_ec_seckey_tweak_add(secp256k1_ctx,
832-
tweakedkey.secret.data,
833-
tweak.u.u8) != 1)
834-
hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR,
835-
"Could tweak bolt12 key.");
851+
node_blinded_privkey(path_pubkey, &blinded_privkey);
836852

837853
if (secp256k1_keypair_create(secp256k1_ctx, &kp,
838-
tweakedkey.secret.data) != 1)
854+
blinded_privkey.secret.data) != 1)
839855
hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR,
840856
"Failed to derive bolt12 keypair");
841857
}

lightningd/invoice.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <common/bolt12_id.h>
1212
#include <common/bolt12_merkle.h>
1313
#include <common/configdir.h>
14+
#include <common/ecdh.h>
1415
#include <common/json_command.h>
1516
#include <common/json_param.h>
1617
#include <common/overflows.h>
@@ -481,15 +482,16 @@ static bool hsm_sign_b11(const u5 *u5bytes,
481482
}
482483

483484
static void hsm_sign_b12_invoice(struct lightningd *ld,
484-
struct tlv_invoice *invoice)
485+
struct tlv_invoice *invoice,
486+
const struct pubkey* path_pubkey)
485487
{
486488
struct sha256 merkle;
487489
const u8 *msg;
488490

489491
assert(!invoice->signature);
490492

491493
merkle_tlv(invoice->fields, &merkle);
492-
msg = towire_hsmd_sign_bolt12(NULL, "invoice", "signature", &merkle, NULL);
494+
msg = towire_hsmd_sign_bolt12(NULL, "invoice", "signature", &merkle, path_pubkey);
493495

494496
msg = hsm_sync_req(tmpctx, ld, take(msg));
495497
invoice->signature = tal(invoice, struct bip340sig);
@@ -1674,6 +1676,7 @@ static struct command_result *json_createinvoice(struct command *cmd,
16741676
const char *invstring;
16751677
struct json_escape *label;
16761678
struct preimage *preimage;
1679+
struct pubkey *path_pubkey;
16771680
u64 inv_dbid;
16781681
struct sha256 payment_hash;
16791682
struct json_stream *response;
@@ -1687,6 +1690,7 @@ static struct command_result *json_createinvoice(struct command *cmd,
16871690
p_req("invstring", param_invstring, &invstring),
16881691
p_req("label", param_label, &label),
16891692
p_req("preimage", param_preimage, &preimage),
1693+
p_opt("path_pubkey", param_pubkey, &path_pubkey),
16901694
NULL))
16911695
return command_param_failed();
16921696

@@ -1760,7 +1764,8 @@ static struct command_result *json_createinvoice(struct command *cmd,
17601764
if (inv->signature)
17611765
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
17621766
"invoice already signed");
1763-
hsm_sign_b12_invoice(cmd->ld, inv);
1767+
1768+
hsm_sign_b12_invoice(cmd->ld, inv, path_pubkey);
17641769
b12enc = invoice_encode(cmd, inv);
17651770

17661771
if (inv->offer_issuer_id || inv->offer_paths) {

lightningd/onion_message.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ struct onion_message_hook_payload {
1818
/* Optional */
1919
struct blinded_path *reply_path;
2020
struct secret *pathsecret;
21+
struct pubkey blinded_node_id;
22+
struct pubkey path_pubkey;
2123
struct tlv_onionmsg_tlv *om;
2224
};
2325

@@ -61,6 +63,9 @@ static void onion_message_serialize(struct onion_message_hook_payload *payload,
6163
if (payload->pathsecret)
6264
json_add_secret(stream, "pathsecret", payload->pathsecret);
6365

66+
json_add_pubkey(stream, "blinded_node_id", &payload->blinded_node_id);
67+
json_add_pubkey(stream, "path_pubkey", &payload->path_pubkey);
68+
6469
if (payload->reply_path)
6570
json_add_blindedpath(plugin, stream, "reply_blindedpath",
6671
payload->reply_path);
@@ -126,7 +131,9 @@ void handle_onionmsg_to_us(struct lightningd *ld, const u8 *msg)
126131
if (!fromwire_connectd_got_onionmsg_to_us(payload, msg,
127132
&payload->pathsecret,
128133
&payload->reply_path,
129-
&submsg)) {
134+
&submsg,
135+
&payload->blinded_node_id,
136+
&payload->path_pubkey)) {
130137
log_broken(ld->log, "bad got_onionmsg_tous: %s",
131138
tal_hex(tmpctx, msg));
132139
return;

lightningd/test/run-invoice-select-inchan.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1008,7 +1008,7 @@ u8 *towire_hsmd_preapprove_keysend(const tal_t *ctx UNNEEDED, const struct node_
10081008
u8 *towire_hsmd_preapprove_keysend_check(const tal_t *ctx UNNEEDED, const struct node_id *destination UNNEEDED, const struct sha256 *payment_hash UNNEEDED, struct amount_msat amount_msat UNNEEDED, bool check_only UNNEEDED)
10091009
{ fprintf(stderr, "towire_hsmd_preapprove_keysend_check called!\n"); abort(); }
10101010
/* Generated stub for towire_hsmd_sign_bolt12 */
1011-
u8 *towire_hsmd_sign_bolt12(const tal_t *ctx UNNEEDED, const wirestring *messagename UNNEEDED, const wirestring *fieldname UNNEEDED, const struct sha256 *merkleroot UNNEEDED, const u8 *publictweak UNNEEDED)
1011+
u8 *towire_hsmd_sign_bolt12(const tal_t *ctx UNNEEDED, const wirestring *messagename UNNEEDED, const wirestring *fieldname UNNEEDED, const struct sha256 *merkleroot UNNEEDED, const struct pubkey *path_pubkey UNNEEDED)
10121012
{ fprintf(stderr, "towire_hsmd_sign_bolt12 called!\n"); abort(); }
10131013
/* Generated stub for towire_hsmd_sign_commitment_tx */
10141014
u8 *towire_hsmd_sign_commitment_tx(const tal_t *ctx UNNEEDED, const struct node_id *peer_id UNNEEDED, u64 channel_dbid UNNEEDED, const struct bitcoin_tx *tx UNNEEDED, const struct pubkey *remote_funding_key UNNEEDED, u64 commit_num UNNEEDED)

plugins/offers.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,11 @@ static struct command_result *onion_message_recv(struct command *cmd,
262262
const char *buf,
263263
const jsmntok_t *params)
264264
{
265-
const jsmntok_t *om, *secrettok, *replytok, *invreqtok, *invtok;
265+
const jsmntok_t *om, *secrettok, *nodeidtok, *pubkeytok, *replytok, *invreqtok, *invtok;
266266
struct blinded_path *reply_path = NULL;
267267
struct secret *secret;
268+
struct pubkey *blinded_node_id;
269+
struct pubkey *path_pubkey;
268270

269271
om = json_get_member(buf, params, "onion_message");
270272
secrettok = json_get_member(buf, om, "pathsecret");
@@ -273,6 +275,14 @@ static struct command_result *onion_message_recv(struct command *cmd,
273275
json_to_secret(buf, secrettok, secret);
274276
} else
275277
secret = NULL;
278+
nodeidtok = json_get_member(buf, om, "blinded_node_id");
279+
if(!nodeidtok) plugin_err(cmd->plugin, "Missing blinded node id");
280+
blinded_node_id = tal(tmpctx, struct pubkey);
281+
json_to_pubkey(buf, nodeidtok, blinded_node_id);
282+
pubkeytok = json_get_member(buf, om, "path_pubkey");
283+
if(!pubkeytok) plugin_err(cmd->plugin, "Missing path pubkey");
284+
path_pubkey = tal(tmpctx, struct pubkey);
285+
json_to_pubkey(buf, pubkeytok, path_pubkey);
276286

277287
/* Might be reply for fetchinvoice (which always has a secret,
278288
* so we can tell it's a response). */
@@ -298,7 +308,7 @@ static struct command_result *onion_message_recv(struct command *cmd,
298308
if (reply_path)
299309
return handle_invoice_request(cmd,
300310
invreqbin,
301-
reply_path, secret);
311+
reply_path, secret, blinded_node_id, path_pubkey);
302312
else
303313
plugin_log(cmd->plugin, LOG_DBG,
304314
"invoice_request without reply_path");

plugins/offers_invreq_hook.c

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ struct invreq {
3939

4040
/* Optional secret. */
4141
const struct secret *secret;
42+
43+
/* The blinded node id the invoice request was received through. */
44+
struct pubkey *blinded_node_id;
45+
46+
/* The path pubkey the invoice request was received through. */
47+
struct pubkey *path_pubkey;
4248
};
4349

4450
static struct command_result *WARN_UNUSED_RESULT
@@ -231,6 +237,7 @@ static struct command_result *create_invoicereq(struct command *cmd,
231237

232238
json_add_string(req->js, "invstring", invoice_encode(tmpctx, ir->inv));
233239
json_add_preimage(req->js, "preimage", &ir->preimage);
240+
if(ir->path_pubkey) json_add_pubkey(req->js, "path_pubkey", ir->path_pubkey);
234241
json_add_label(req->js, &ir->offer_id, ir->inv->invreq_payer_id,
235242
ir->inv->invreq_recurrence_counter
236243
? *ir->inv->invreq_recurrence_counter : 0);
@@ -935,11 +942,15 @@ static struct command_result *listoffers_done(struct command *cmd,
935942
* - MUST set `invoice_node_id` to the `offer_issuer_id`
936943
* - else:
937944
* - MUST set `invoice_node_id` to the final `blinded_node_id`
945+
* the finale blinded_node_id
938946
*/
939947
if(!ir->inv->offer_issuer_id && ir->invreq->offer_paths) {
940-
ir->inv->invoice_node_id = &(*ir->invreq->offer_paths)->path[tal_count((*ir->invreq->offer_paths)->path)-1]->blinded_node_id;
948+
ir->inv->invoice_node_id = ir->blinded_node_id;
941949

942-
} else ir->inv->invoice_node_id = ir->inv->offer_issuer_id;
950+
} else {
951+
ir->inv->invoice_node_id = ir->inv->offer_issuer_id;
952+
ir->path_pubkey = NULL;
953+
}
943954

944955
/* BOLT #12:
945956
* - MUST set `invoice_created_at` to the number of seconds since
@@ -977,7 +988,9 @@ static struct command_result *listoffers_done(struct command *cmd,
977988
struct command_result *handle_invoice_request(struct command *cmd,
978989
const u8 *invreqbin,
979990
struct blinded_path *reply_path,
980-
const struct secret *secret)
991+
const struct secret *secret,
992+
const struct pubkey *blinded_node_id,
993+
const struct pubkey *path_pubkey)
981994
{
982995
struct out_req *req;
983996
int bad_feature;
@@ -987,6 +1000,8 @@ struct command_result *handle_invoice_request(struct command *cmd,
9871000

9881001
ir->reply_path = tal_steal(ir, reply_path);
9891002
ir->secret = tal_dup_or_null(ir, struct secret, secret);
1003+
ir->blinded_node_id = tal_dup(ir, struct pubkey, blinded_node_id);
1004+
ir->path_pubkey = tal_dup(ir, struct pubkey, path_pubkey);
9901005
ir->invreq = fromwire_tlv_invoice_request(cmd, &cursor, &len);
9911006

9921007
if (!ir->invreq) {

0 commit comments

Comments
 (0)