Skip to content

Commit 206ea1b

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

File tree

10 files changed

+92
-38
lines changed

10 files changed

+92
-38
lines changed

connectd/connectd_wire.csv

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ msgdata,connectd_got_onionmsg_to_us,path_secret,?secret,
157157
msgdata,connectd_got_onionmsg_to_us,reply,?blinded_path,
158158
msgdata,connectd_got_onionmsg_to_us,rawmsg_len,u16,
159159
msgdata,connectd_got_onionmsg_to_us,rawmsg,u8,rawmsg_len
160+
msgdata,connectd_got_onionmsg_to_us,final_alias,pubkey,
161+
msgdata,connectd_got_onionmsg_to_us,path_pubkey,pubkey,
160162

161163
# Lightningd tells us to send an onion message.
162164
msgtype,connectd_send_onionmsg,2041

connectd/onion_message.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ static const char *handle_onion(const tal_t *ctx,
7272
take(towire_connectd_got_onionmsg_to_us(NULL,
7373
final_path_id,
7474
final_om->reply_path,
75-
omsg)));
75+
omsg, &final_alias, path_key)));
7676
} else {
7777
struct node_id next_node_id;
7878
struct peer *next_peer;

hsmd/hsmd_wire.csv

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -403,9 +403,7 @@ msgtype,hsmd_sign_bolt12,25
403403
msgdata,hsmd_sign_bolt12,messagename,wirestring,
404404
msgdata,hsmd_sign_bolt12,fieldname,wirestring,
405405
msgdata,hsmd_sign_bolt12,merkleroot,sha256,
406-
# This is for invreq payer_id (temporary keys)
407-
msgdata,hsmd_sign_bolt12,publictweaklen,u16,
408-
msgdata,hsmd_sign_bolt12,publictweak,u8,publictweaklen
406+
msgdata,hsmd_sign_bolt12,path_pubkey,?pubkey,
409407

410408
#include <bitcoin/signature.h>
411409
msgtype,hsmd_sign_bolt12_reply,125

hsmd/libhsmd.c

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

819+
/*
819820
static void payer_key_tweak(const struct pubkey *bolt12,
820821
const u8 *publictweak, size_t publictweaklen,
821822
struct sha256 *tweak)
@@ -830,48 +831,63 @@ static void payer_key_tweak(const struct pubkey *bolt12,
830831
sha256_update(&sha, publictweak, publictweaklen);
831832
sha256_done(&sha, tweak);
832833
}
834+
*/
833835

834-
/*~ lightningd asks us to sign a bolt12 (e.g. offer). */
836+
static void node_blinded_privkey(const struct pubkey *path_pubkey, struct privkey *blinded_privkey)
837+
{
838+
struct secret ss;
839+
struct secret node_id_blinding;
840+
841+
node_key(blinded_privkey, NULL);
842+
843+
/* BOLT #4:
844+
* - $`ss_i = SHA256(e_i * N_i) = SHA256(k_i * E_i)`$
845+
* (ECDH shared secret known only by $`N_r`$ and $`N_i`$)
846+
*/
847+
if (secp256k1_ecdh(secp256k1_ctx, ss.data,
848+
&path_pubkey->pubkey, blinded_privkey->secret.data,
849+
NULL, NULL) != 1)
850+
hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR,
851+
"Could not compute ss from path_key.");
852+
853+
/* BOLT #4:
854+
* - $`B_i = HMAC256(\text{"blinded\_node\_id"}, ss_i) * N_i`$
855+
* (blinded `node_id` for $`N_i`$, private key known only by $`N_i`$)
856+
*/
857+
subkey_from_hmac("blinded_node_id", &ss, &node_id_blinding);
858+
859+
if (secp256k1_ec_seckey_tweak_mul(secp256k1_ctx,
860+
blinded_privkey->secret.data,
861+
node_id_blinding.data) != 1)
862+
hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR,
863+
"Could tweak bolt12 key.");
864+
}
865+
866+
/*~ lightningd asks us to sign a bolt12 invoice. */
835867
static u8 *handle_sign_bolt12(struct hsmd_client *c, const u8 *msg_in)
836868
{
837869
char *messagename, *fieldname;
838870
struct sha256 merkle, sha;
839871
struct bip340sig sig;
840872
secp256k1_keypair kp;
841-
u8 *publictweak;
873+
struct pubkey* path_pubkey;
842874

843875
if (!fromwire_hsmd_sign_bolt12(tmpctx, msg_in,
844876
&messagename, &fieldname, &merkle,
845-
&publictweak))
877+
&path_pubkey))
846878
return hsmd_status_malformed_request(c, msg_in);
847879

848880
sighash_from_merkle(messagename, fieldname, &merkle, &sha);
849881

850-
if (!publictweak) {
882+
if (!path_pubkey) {
851883
node_schnorrkey(&kp);
852884
} else {
853-
/* If we're tweaking key, we use bolt12 key */
854-
struct privkey tweakedkey;
855-
struct pubkey bolt12;
856-
struct sha256 tweak;
885+
struct privkey blinded_privkey;
857886

858-
if (secp256k1_ec_pubkey_create(secp256k1_ctx, &bolt12.pubkey,
859-
secretstuff.bolt12.data) != 1)
860-
hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR,
861-
"Could derive bolt12 public key.");
862-
863-
payer_key_tweak(&bolt12, publictweak, tal_bytelen(publictweak),
864-
&tweak);
865-
866-
tweakedkey.secret = secretstuff.bolt12;
867-
if (secp256k1_ec_seckey_tweak_add(secp256k1_ctx,
868-
tweakedkey.secret.data,
869-
tweak.u.u8) != 1)
870-
hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR,
871-
"Could tweak bolt12 key.");
887+
node_blinded_privkey(path_pubkey, &blinded_privkey);
872888

873889
if (secp256k1_keypair_create(secp256k1_ctx, &kp,
874-
tweakedkey.secret.data) != 1)
890+
blinded_privkey.secret.data) != 1)
875891
hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR,
876892
"Failed to derive bolt12 keypair");
877893
}

lightningd/invoice.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -478,15 +478,16 @@ static bool hsm_sign_b11(const u5 *u5bytes,
478478
}
479479

480480
static void hsm_sign_b12_invoice(struct lightningd *ld,
481-
struct tlv_invoice *invoice)
481+
struct tlv_invoice *invoice,
482+
const struct pubkey* path_pubkey)
482483
{
483484
struct sha256 merkle;
484485
const u8 *msg;
485486

486487
assert(!invoice->signature);
487488

488489
merkle_tlv(invoice->fields, &merkle);
489-
msg = towire_hsmd_sign_bolt12(NULL, "invoice", "signature", &merkle, NULL);
490+
msg = towire_hsmd_sign_bolt12(NULL, "invoice", "signature", &merkle, path_pubkey);
490491

491492
msg = hsm_sync_req(tmpctx, ld, take(msg));
492493
invoice->signature = tal(invoice, struct bip340sig);
@@ -1671,6 +1672,7 @@ static struct command_result *json_createinvoice(struct command *cmd,
16711672
const char *invstring;
16721673
struct json_escape *label;
16731674
struct preimage *preimage;
1675+
struct pubkey *path_pubkey;
16741676
u64 inv_dbid;
16751677
struct sha256 payment_hash;
16761678
struct json_stream *response;
@@ -1684,6 +1686,7 @@ static struct command_result *json_createinvoice(struct command *cmd,
16841686
p_req("invstring", param_invstring, &invstring),
16851687
p_req("label", param_label, &label),
16861688
p_req("preimage", param_preimage, &preimage),
1689+
p_opt("path_pubkey", param_pubkey, &path_pubkey),
16871690
NULL))
16881691
return command_param_failed();
16891692

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

17631767
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
@@ -12,6 +12,8 @@ struct onion_message_hook_payload {
1212
/* Optional */
1313
struct blinded_path *reply_path;
1414
struct secret *pathsecret;
15+
struct pubkey blinded_node_id;
16+
struct pubkey path_pubkey;
1517
struct tlv_onionmsg_tlv *om;
1618
};
1719

@@ -49,6 +51,9 @@ static void onion_message_serialize(struct onion_message_hook_payload *payload,
4951
if (payload->pathsecret)
5052
json_add_secret(stream, "pathsecret", payload->pathsecret);
5153

54+
json_add_pubkey(stream, "blinded_node_id", &payload->blinded_node_id);
55+
json_add_pubkey(stream, "path_pubkey", &payload->path_pubkey);
56+
5257
if (payload->reply_path)
5358
json_add_blindedpath(plugin, stream, "reply_blindedpath",
5459
payload->reply_path);
@@ -114,7 +119,9 @@ void handle_onionmsg_to_us(struct lightningd *ld, const u8 *msg)
114119
if (!fromwire_connectd_got_onionmsg_to_us(payload, msg,
115120
&payload->pathsecret,
116121
&payload->reply_path,
117-
&submsg)) {
122+
&submsg,
123+
&payload->blinded_node_id,
124+
&payload->path_pubkey)) {
118125
log_broken(ld->log, "bad got_onionmsg_tous: %s",
119126
tal_hex(tmpctx, msg));
120127
return;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,7 @@ u8 *towire_hsmd_preapprove_keysend(const tal_t *ctx UNNEEDED, const struct node_
633633
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)
634634
{ fprintf(stderr, "towire_hsmd_preapprove_keysend_check called!\n"); abort(); }
635635
/* Generated stub for towire_hsmd_sign_bolt12 */
636-
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)
636+
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)
637637
{ fprintf(stderr, "towire_hsmd_sign_bolt12 called!\n"); abort(); }
638638
/* Generated stub for towire_hsmd_sign_commitment_tx */
639639
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
@@ -261,9 +261,11 @@ static struct command_result *onion_message_recv(struct command *cmd,
261261
const char *buf,
262262
const jsmntok_t *params)
263263
{
264-
const jsmntok_t *om, *secrettok, *replytok, *invreqtok, *invtok;
264+
const jsmntok_t *om, *secrettok, *nodeidtok, *pubkeytok, *replytok, *invreqtok, *invtok;
265265
struct blinded_path *reply_path = NULL;
266266
struct secret *secret;
267+
struct pubkey *blinded_node_id;
268+
struct pubkey *path_pubkey;
267269

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

276286
/* Might be reply for fetchinvoice (which always has a secret,
277287
* so we can tell it's a response). */
@@ -296,7 +306,7 @@ static struct command_result *onion_message_recv(struct command *cmd,
296306
const u8 *invreqbin = json_tok_bin_from_hex(tmpctx, buf, invreqtok);
297307
return handle_invoice_request(cmd,
298308
invreqbin,
299-
reply_path, secret);
309+
reply_path, secret, blinded_node_id, path_pubkey);
300310
}
301311

302312
invtok = json_get_member(buf, om, "invoice");

plugins/offers_invreq_hook.c

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

3636
/* Optional secret. */
3737
const struct secret *secret;
38+
39+
/* The blinded node id the invoice request was received through. */
40+
struct pubkey *blinded_node_id;
41+
42+
/* The path pubkey the invoice request was received through. */
43+
struct pubkey *path_pubkey;
3844
};
3945

4046
static struct command_result *WARN_UNUSED_RESULT
@@ -243,6 +249,7 @@ static struct command_result *create_invoicereq(struct command *cmd,
243249

244250
json_add_string(req->js, "invstring", invoice_encode(tmpctx, ir->inv));
245251
json_add_preimage(req->js, "preimage", &ir->preimage);
252+
if(ir->path_pubkey) json_add_pubkey(req->js, "path_pubkey", ir->path_pubkey);
246253
json_add_label(req->js, &ir->offer_id, ir->inv->invreq_payer_id,
247254
ir->inv->invreq_recurrence_counter
248255
? *ir->inv->invreq_recurrence_counter : 0);
@@ -993,11 +1000,15 @@ static struct command_result *listoffers_done(struct command *cmd,
9931000
* - MUST set `invoice_node_id` to the `offer_issuer_id`
9941001
* - else:
9951002
* - MUST set `invoice_node_id` to the final `blinded_node_id`
1003+
* the finale blinded_node_id
9961004
*/
9971005
if(!ir->inv->offer_issuer_id && ir->invreq->offer_paths) {
998-
ir->inv->invoice_node_id = &(*ir->invreq->offer_paths)->path[tal_count((*ir->invreq->offer_paths)->path)-1]->blinded_node_id;
1006+
ir->inv->invoice_node_id = ir->blinded_node_id;
9991007

1000-
} else ir->inv->invoice_node_id = ir->inv->offer_issuer_id;
1008+
} else {
1009+
ir->inv->invoice_node_id = ir->inv->offer_issuer_id;
1010+
ir->path_pubkey = NULL;
1011+
}
10011012

10021013
/* BOLT #12:
10031014
* - MUST set `invoice_created_at` to the number of seconds since
@@ -1035,7 +1046,9 @@ static struct command_result *listoffers_done(struct command *cmd,
10351046
struct command_result *handle_invoice_request(struct command *cmd,
10361047
const u8 *invreqbin,
10371048
struct blinded_path *reply_path,
1038-
const struct secret *secret)
1049+
const struct secret *secret,
1050+
const struct pubkey *blinded_node_id,
1051+
const struct pubkey *path_pubkey)
10391052
{
10401053
struct out_req *req;
10411054
int bad_feature;
@@ -1045,6 +1058,8 @@ struct command_result *handle_invoice_request(struct command *cmd,
10451058

10461059
ir->reply_path = tal_steal(ir, reply_path);
10471060
ir->secret = tal_dup_or_null(ir, struct secret, secret);
1061+
ir->blinded_node_id = tal_dup(ir, struct pubkey, blinded_node_id);
1062+
ir->path_pubkey = tal_dup(ir, struct pubkey, path_pubkey);
10481063
ir->invreq = fromwire_tlv_invoice_request(cmd, &cursor, &len);
10491064

10501065
if (!ir->invreq) {

plugins/offers_invreq_hook.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,7 @@
77
struct command_result *handle_invoice_request(struct command *cmd,
88
const u8 *invreqbin,
99
struct blinded_path *reply_path STEALS,
10-
const struct secret *secret TAKES);
10+
const struct secret *secret TAKES,
11+
const struct pubkey *blinded_node_id TAKES,
12+
const struct pubkey *path_pubkey TAKES);
1113
#endif /* LIGHTNING_PLUGINS_OFFERS_INVREQ_HOOK_H */

0 commit comments

Comments
 (0)