diff --git a/common/hsm_version.h b/common/hsm_version.h index 8b6c54bdfa51..3c8cf77ba9be 100644 --- a/common/hsm_version.h +++ b/common/hsm_version.h @@ -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 */ diff --git a/connectd/connectd_wire.csv b/connectd/connectd_wire.csv index d3af2709d545..e0577bffdaa0 100644 --- a/connectd/connectd_wire.csv +++ b/connectd/connectd_wire.csv @@ -170,6 +170,8 @@ 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, +msgdata,connectd_got_onionmsg_to_us,path_pubkey,pubkey, # Lightningd tells us to send an onion message. msgtype,connectd_send_onionmsg,2041 diff --git a/connectd/onion_message.c b/connectd/onion_message.c index 27718b757934..45294555a1cb 100644 --- a/connectd/onion_message.c +++ b/connectd/onion_message.c @@ -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, path_key))); } else { struct node_id next_node_id; struct peer *next_peer; diff --git a/hsmd/hsmd_wire.csv b/hsmd/hsmd_wire.csv index 7336feef4900..ff0dd7f2874b 100644 --- a/hsmd/hsmd_wire.csv +++ b/hsmd/hsmd_wire.csv @@ -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 msgtype,hsmd_sign_bolt12_reply,125 diff --git a/hsmd/libhsmd.c b/hsmd/libhsmd.c index e4a14e44cf14..e96a7812787e 100644 --- a/hsmd/libhsmd.c +++ b/hsmd/libhsmd.c @@ -816,6 +816,7 @@ static u8 *handle_sign_option_will_fund_offer(struct hsmd_client *c, return towire_hsmd_sign_option_will_fund_offer_reply(NULL, &sig); } +/* static void payer_key_tweak(const struct pubkey *bolt12, const u8 *publictweak, size_t publictweaklen, struct sha256 *tweak) @@ -830,48 +831,63 @@ static void payer_key_tweak(const struct pubkey *bolt12, sha256_update(&sha, publictweak, publictweaklen); sha256_done(&sha, tweak); } +*/ -/*~ lightningd asks us to sign a bolt12 (e.g. offer). */ +static void node_blinded_privkey(const struct pubkey *path_pubkey, struct privkey *blinded_privkey) +{ + struct secret ss; + struct secret node_id_blinding; + + node_key(blinded_privkey, NULL); + + /* 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, + &path_pubkey->pubkey, blinded_privkey->secret.data, + NULL, NULL) != 1) + hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR, + "Could not compute ss from path_key."); + + /* BOLT #4: + * - $`B_i = HMAC256(\text{"blinded\_node\_id"}, ss_i) * N_i`$ + * (blinded `node_id` for $`N_i`$, private key known only by $`N_i`$) + */ + subkey_from_hmac("blinded_node_id", &ss, &node_id_blinding); + + if (secp256k1_ec_seckey_tweak_mul(secp256k1_ctx, + blinded_privkey->secret.data, + node_id_blinding.data) != 1) + hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR, + "Could tweak bolt12 key."); +} + +/*~ lightningd asks us to sign a bolt12 invoice. */ static u8 *handle_sign_bolt12(struct hsmd_client *c, const u8 *msg_in) { char *messagename, *fieldname; struct sha256 merkle, sha; struct bip340sig sig; secp256k1_keypair kp; - u8 *publictweak; + struct pubkey* path_pubkey; if (!fromwire_hsmd_sign_bolt12(tmpctx, msg_in, &messagename, &fieldname, &merkle, - &publictweak)) + &path_pubkey)) return hsmd_status_malformed_request(c, msg_in); sighash_from_merkle(messagename, fieldname, &merkle, &sha); - if (!publictweak) { + if (!path_pubkey) { node_schnorrkey(&kp); } else { - /* If we're tweaking key, we use bolt12 key */ - struct privkey tweakedkey; - struct pubkey bolt12; - struct sha256 tweak; + struct privkey blinded_privkey; - if (secp256k1_ec_pubkey_create(secp256k1_ctx, &bolt12.pubkey, - secretstuff.bolt12.data) != 1) - hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Could derive bolt12 public key."); - - payer_key_tweak(&bolt12, publictweak, tal_bytelen(publictweak), - &tweak); - - tweakedkey.secret = secretstuff.bolt12; - if (secp256k1_ec_seckey_tweak_add(secp256k1_ctx, - tweakedkey.secret.data, - tweak.u.u8) != 1) - hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Could tweak bolt12 key."); + node_blinded_privkey(path_pubkey, &blinded_privkey); if (secp256k1_keypair_create(secp256k1_ctx, &kp, - tweakedkey.secret.data) != 1) + blinded_privkey.secret.data) != 1) hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR, "Failed to derive bolt12 keypair"); } diff --git a/lightningd/invoice.c b/lightningd/invoice.c index aa9546850404..dd968bae821a 100644 --- a/lightningd/invoice.c +++ b/lightningd/invoice.c @@ -479,7 +479,8 @@ static bool hsm_sign_b11(const u5 *u5bytes, } static void hsm_sign_b12_invoice(struct lightningd *ld, - struct tlv_invoice *invoice) + struct tlv_invoice *invoice, + const struct pubkey* path_pubkey) { struct sha256 merkle; const u8 *msg; @@ -487,7 +488,7 @@ static void hsm_sign_b12_invoice(struct lightningd *ld, assert(!invoice->signature); merkle_tlv(invoice->fields, &merkle); - msg = towire_hsmd_sign_bolt12(NULL, "invoice", "signature", &merkle, NULL); + msg = towire_hsmd_sign_bolt12(NULL, "invoice", "signature", &merkle, path_pubkey); msg = hsm_sync_req(tmpctx, ld, take(msg)); invoice->signature = tal(invoice, struct bip340sig); @@ -1672,6 +1673,7 @@ static struct command_result *json_createinvoice(struct command *cmd, const char *invstring; struct json_escape *label; struct preimage *preimage; + struct pubkey *path_pubkey; u64 inv_dbid; struct sha256 payment_hash; struct json_stream *response; @@ -1685,6 +1687,7 @@ static struct command_result *json_createinvoice(struct command *cmd, p_req("invstring", param_invstring, &invstring), p_req("label", param_label, &label), p_req("preimage", param_preimage, &preimage), + p_opt("path_pubkey", param_pubkey, &path_pubkey), NULL)) return command_param_failed(); @@ -1758,7 +1761,8 @@ static struct command_result *json_createinvoice(struct command *cmd, if (inv->signature) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "invoice already signed"); - hsm_sign_b12_invoice(cmd->ld, inv); + + hsm_sign_b12_invoice(cmd->ld, inv, path_pubkey); b12enc = invoice_encode(cmd, inv); if (inv->offer_issuer_id || inv->offer_paths) { diff --git a/lightningd/onion_message.c b/lightningd/onion_message.c index 70efb66e53d1..1614f867b2fc 100644 --- a/lightningd/onion_message.c +++ b/lightningd/onion_message.c @@ -12,6 +12,8 @@ struct onion_message_hook_payload { /* Optional */ struct blinded_path *reply_path; struct secret *pathsecret; + struct pubkey blinded_node_id; + struct pubkey path_pubkey; struct tlv_onionmsg_tlv *om; }; @@ -49,6 +51,9 @@ static void onion_message_serialize(struct onion_message_hook_payload *payload, if (payload->pathsecret) json_add_secret(stream, "pathsecret", payload->pathsecret); + json_add_pubkey(stream, "blinded_node_id", &payload->blinded_node_id); + json_add_pubkey(stream, "path_pubkey", &payload->path_pubkey); + if (payload->reply_path) json_add_blindedpath(plugin, stream, "reply_blindedpath", payload->reply_path); @@ -114,7 +119,9 @@ void handle_onionmsg_to_us(struct lightningd *ld, const u8 *msg) if (!fromwire_connectd_got_onionmsg_to_us(payload, msg, &payload->pathsecret, &payload->reply_path, - &submsg)) { + &submsg, + &payload->blinded_node_id, + &payload->path_pubkey)) { log_broken(ld->log, "bad got_onionmsg_tous: %s", tal_hex(tmpctx, msg)); return; diff --git a/lightningd/test/run-invoice-select-inchan.c b/lightningd/test/run-invoice-select-inchan.c index fc00a7c9c25e..d2ef8054fb3b 100644 --- a/lightningd/test/run-invoice-select-inchan.c +++ b/lightningd/test/run-invoice-select-inchan.c @@ -650,7 +650,7 @@ u8 *towire_hsmd_preapprove_keysend(const tal_t *ctx UNNEEDED, const struct node_ 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) { fprintf(stderr, "towire_hsmd_preapprove_keysend_check called!\n"); abort(); } /* Generated stub for towire_hsmd_sign_bolt12 */ -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) +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) { fprintf(stderr, "towire_hsmd_sign_bolt12 called!\n"); abort(); } /* Generated stub for towire_hsmd_sign_commitment_tx */ 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) diff --git a/plugins/offers.c b/plugins/offers.c index 2cefc1ff7cf7..020b08cdeee6 100644 --- a/plugins/offers.c +++ b/plugins/offers.c @@ -263,9 +263,11 @@ static struct command_result *onion_message_recv(struct command *cmd, const char *buf, const jsmntok_t *params) { - const jsmntok_t *om, *secrettok, *replytok, *invreqtok, *invtok; + const jsmntok_t *om, *secrettok, *nodeidtok, *pubkeytok, *replytok, *invreqtok, *invtok; struct blinded_path *reply_path = NULL; struct secret *secret; + struct pubkey *blinded_node_id; + struct pubkey *path_pubkey; om = json_get_member(buf, params, "onion_message"); secrettok = json_get_member(buf, om, "pathsecret"); @@ -274,6 +276,14 @@ static struct command_result *onion_message_recv(struct command *cmd, json_to_secret(buf, secrettok, secret); } else secret = NULL; + nodeidtok = json_get_member(buf, om, "blinded_node_id"); + if(!nodeidtok) plugin_err(cmd->plugin, "Missing blinded node id"); + blinded_node_id = tal(tmpctx, struct pubkey); + json_to_pubkey(buf, nodeidtok, blinded_node_id); + pubkeytok = json_get_member(buf, om, "path_pubkey"); + if(!pubkeytok) plugin_err(cmd->plugin, "Missing path pubkey"); + path_pubkey = tal(tmpctx, struct pubkey); + json_to_pubkey(buf, pubkeytok, path_pubkey); /* Might be reply for fetchinvoice (which always has a secret, * so we can tell it's a response). */ @@ -298,7 +308,7 @@ static struct command_result *onion_message_recv(struct command *cmd, const u8 *invreqbin = json_tok_bin_from_hex(tmpctx, buf, invreqtok); return handle_invoice_request(cmd, invreqbin, - reply_path, secret); + reply_path, secret, blinded_node_id, path_pubkey); } invtok = json_get_member(buf, om, "invoice"); diff --git a/plugins/offers_invreq_hook.c b/plugins/offers_invreq_hook.c index 8588ed25b030..7097178690fd 100644 --- a/plugins/offers_invreq_hook.c +++ b/plugins/offers_invreq_hook.c @@ -36,6 +36,12 @@ struct invreq { /* Optional secret. */ const struct secret *secret; + + /* The blinded node id the invoice request was received through. */ + struct pubkey *blinded_node_id; + + /* The path pubkey the invoice request was received through. */ + struct pubkey *path_pubkey; }; static struct command_result *WARN_UNUSED_RESULT @@ -244,6 +250,7 @@ static struct command_result *create_invoicereq(struct command *cmd, json_add_string(req->js, "invstring", invoice_encode(tmpctx, ir->inv)); json_add_preimage(req->js, "preimage", &ir->preimage); + if(ir->path_pubkey) json_add_pubkey(req->js, "path_pubkey", ir->path_pubkey); json_add_label(req->js, &ir->offer_id, ir->inv->invreq_payer_id, ir->inv->invreq_recurrence_counter ? *ir->inv->invreq_recurrence_counter : 0); @@ -995,11 +1002,19 @@ static struct command_result *listoffers_done(struct command *cmd, ir->invreq->invreq_recurrence_cancel = cancel; /* BOLT #12: - * - if `offer_issuer_id` is present: - * - MUST set `invoice_node_id` to the `offer_issuer_id` + * - if `offer_issuer_id` is present: + * - MUST set `invoice_node_id` to the `offer_issuer_id` + * - otherwise, if `offer_paths` is present: + * - MUST set `invoice_node_id` to the final `blinded_node_id` on the + * path it received the invoice request */ - /* FIXME: We always provide an offer_issuer_id! */ - ir->inv->invoice_node_id = ir->inv->offer_issuer_id; + if(!ir->inv->offer_issuer_id && ir->invreq->offer_paths) { + ir->inv->invoice_node_id = ir->blinded_node_id; + + } else { + ir->inv->invoice_node_id = ir->inv->offer_issuer_id; + ir->path_pubkey = NULL; + } /* BOLT #12: * - MUST set `invoice_created_at` to the number of seconds since @@ -1037,7 +1052,9 @@ static struct command_result *listoffers_done(struct command *cmd, struct command_result *handle_invoice_request(struct command *cmd, const u8 *invreqbin, struct blinded_path *reply_path, - const struct secret *secret) + const struct secret *secret, + const struct pubkey *blinded_node_id, + const struct pubkey *path_pubkey) { struct out_req *req; int bad_feature; @@ -1047,6 +1064,8 @@ struct command_result *handle_invoice_request(struct command *cmd, ir->reply_path = tal_steal(ir, reply_path); ir->secret = tal_dup_or_null(ir, struct secret, secret); + ir->blinded_node_id = tal_dup(ir, struct pubkey, blinded_node_id); + ir->path_pubkey = tal_dup(ir, struct pubkey, path_pubkey); ir->invreq = fromwire_tlv_invoice_request(cmd, &cursor, &len); if (!ir->invreq) { diff --git a/plugins/offers_invreq_hook.h b/plugins/offers_invreq_hook.h index a0d63761c066..713b0eaa0a27 100644 --- a/plugins/offers_invreq_hook.h +++ b/plugins/offers_invreq_hook.h @@ -7,5 +7,7 @@ struct command_result *handle_invoice_request(struct command *cmd, const u8 *invreqbin, struct blinded_path *reply_path STEALS, - const struct secret *secret TAKES); + const struct secret *secret TAKES, + const struct pubkey *blinded_node_id TAKES, + const struct pubkey *path_pubkey TAKES); #endif /* LIGHTNING_PLUGINS_OFFERS_INVREQ_HOOK_H */ diff --git a/plugins/offers_offer.c b/plugins/offers_offer.c index 8bad82496d75..f417f751ef58 100644 --- a/plugins/offers_offer.c +++ b/plugins/offers_offer.c @@ -269,6 +269,12 @@ static struct command_result *found_best_peer(struct command *cmd, struct secret blinding_path_secret; struct sha256 offer_id; + /* offer_issuer_id is not needed when offer_paths are used. + * The following line seems to produce a valid offer with + * offer_issuer_id removed. + */ + offinfo->offer->offer_issuer_id = NULL; + /* Note: "id" of offer minus paths */ offer_offer_id(offinfo->offer, &offer_id);