From 59efd160c15f9a5e92f5fe7c1ac11da5885f89a7 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 16 Dec 2020 13:43:37 +1030 Subject: [PATCH] hsmd: code to sign bolt12 messages with a tweaked key. Invoices are signed with our own key, but we use a transient payer_key with a tweak for invoice_requests (and refunds). Signed-off-by: Rusty Russell --- common/bolt12_merkle.c | 18 +++++ common/bolt12_merkle.h | 8 +++ hsmd/hsmd.c | 79 +++++++++++++++++++-- hsmd/hsmd_wire.csv | 4 ++ hsmd/hsmd_wiregen.c | 23 ++++-- hsmd/hsmd_wiregen.h | 10 +-- lightningd/hsm_control.c | 3 +- lightningd/invoice.c | 2 +- lightningd/lightningd.h | 3 + lightningd/offer.c | 2 +- lightningd/test/run-invoice-select-inchan.c | 2 +- 11 files changed, 134 insertions(+), 20 deletions(-) diff --git a/common/bolt12_merkle.c b/common/bolt12_merkle.c index 42d904722..a745cf6e2 100644 --- a/common/bolt12_merkle.c +++ b/common/bolt12_merkle.c @@ -129,3 +129,21 @@ void sighash_from_merkle(const char *messagename, sha256_update(&sctx, merkle, sizeof(*merkle)); sha256_done(&sctx, sighash); } + +/* We use the SHA(pubkey | publictweak); so reader cannot figure out the + * tweak and derive the base key */ +void payer_key_tweak(const struct pubkey32 *bolt12, + const u8 *publictweak, size_t publictweaklen, + struct sha256 *tweak) +{ + u8 rawkey[32]; + struct sha256_ctx sha; + + secp256k1_xonly_pubkey_serialize(secp256k1_ctx, rawkey, &bolt12->pubkey); + sha256_init(&sha); + sha256_update(&sha, rawkey, sizeof(rawkey)); + sha256_update(&sha, + memcheck(publictweak, publictweaklen), + publictweaklen); + sha256_done(&sha, tweak); +} diff --git a/common/bolt12_merkle.h b/common/bolt12_merkle.h index adc8009aa..b7ff27f8c 100644 --- a/common/bolt12_merkle.h +++ b/common/bolt12_merkle.h @@ -21,4 +21,12 @@ void sighash_from_merkle(const char *messagename, const char *fieldname, const struct sha256 *merkle, struct sha256 *sighash); + +/** + * payer_key_tweak - get the actual tweak to use for a payer_key + */ +void payer_key_tweak(const struct pubkey32 *bolt12, + const u8 *publictweak, size_t publictweaklen, + struct sha256 *tweak); + #endif /* LIGHTNING_COMMON_BOLT12_MERKLE_H */ diff --git a/hsmd/hsmd.c b/hsmd/hsmd.c index 8f42a5164..0acd0bf53 100644 --- a/hsmd/hsmd.c +++ b/hsmd/hsmd.c @@ -63,10 +63,11 @@ #define REQ_FD 3 /*~ Nobody will ever find it here! hsm_secret is our root secret, the bip32 - * tree is derived from that, and cached here. */ + * tree and bolt12 payer_id keys are derived from that, and cached here. */ static struct { struct secret hsm_secret; struct ext_key bip32; + secp256k1_keypair bolt12; } secretstuff; /* Version codes for BIP32 extended keys in libwally-core. @@ -490,6 +491,38 @@ static void populate_secretstuff(void) &secretstuff.bip32) != WALLY_OK) status_failed(STATUS_FAIL_INTERNAL_ERROR, "Can't derive private bip32 key"); + + /* BIP 33: + * + * We propose the first level of BIP32 tree structure to be used as + * "purpose". This purpose determines the further structure beneath + * this node. + * + * m / purpose' / * + * + * Apostrophe indicates that BIP32 hardened derivation is used. + * + * We encourage different schemes to apply for assigning a separate + * BIP number and use the same number for purpose field, so addresses + * won't be generated from overlapping BIP32 spaces. + * + * Example: Scheme described in BIP44 should use 44' (or 0x8000002C) + * as purpose. + */ + /* Clearly, we should use 9735, the unicode point for lightning! */ + if (bip32_key_from_parent(&master_extkey, + BIP32_INITIAL_HARDENED_CHILD|9735, + BIP32_FLAG_KEY_PRIVATE, + &child_extkey) != WALLY_OK) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "Can't derive bolt12 bip32 key"); + + /* libwally says: The private key with prefix byte 0; remove it + * for libsecp256k1. */ + if (secp256k1_keypair_create(secp256k1_ctx, &secretstuff.bolt12, + child_extkey.priv_key+1) != 1) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "Can't derive bolt12 keypair"); } /*~ Get the keys for this given BIP32 index: if privkey is NULL, we @@ -706,6 +739,7 @@ static struct io_plan *init_hsm(struct io_conn *conn, { struct node_id node_id; struct pubkey key; + struct pubkey32 bolt12; struct privkey *privkey; struct secret *seed; struct secrets *secrets; @@ -756,12 +790,19 @@ static struct io_plan *init_hsm(struct io_conn *conn, node_key(NULL, &key); node_id_from_pubkey(&node_id, &key); + /* We also give it the base key for bolt12 payerids */ + if (secp256k1_keypair_xonly_pub(secp256k1_ctx, &bolt12.pubkey, NULL, + &secretstuff.bolt12) != 1) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "Could derive bolt12 public key."); + /*~ Note: marshalling a bip32 tree only marshals the public side, * not the secrets! So we're not actually handing them out here! */ return req_reply(conn, c, take(towire_hsmd_init_reply(NULL, &node_id, - &secretstuff.bip32))); + &secretstuff.bip32, + &bolt12))); } /*~ The client has asked us to extract the shared secret from an EC Diffie @@ -1830,18 +1871,44 @@ static struct io_plan *handle_sign_bolt12(struct io_conn *conn, char *messagename, *fieldname; struct sha256 merkle, sha; struct bip340sig sig; - secp256k1_keypair node_kp; + secp256k1_keypair kp; + u8 *publictweak; if (!fromwire_hsmd_sign_bolt12(tmpctx, msg_in, - &messagename, &fieldname, &merkle)) + &messagename, &fieldname, &merkle, + &publictweak)) return bad_req(conn, c, msg_in); sighash_from_merkle(messagename, fieldname, &merkle, &sha); - node_schnorrkey(&node_kp, NULL); + if (!publictweak) { + node_schnorrkey(&kp, NULL); + } else { + /* If we're tweaking key, we use bolt12 key */ + struct pubkey32 bolt12; + struct sha256 tweak; + + if (secp256k1_keypair_xonly_pub(secp256k1_ctx, + &bolt12.pubkey, NULL, + &secretstuff.bolt12) != 1) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "Could not derive bolt12 public key."); + payer_key_tweak(&bolt12, publictweak, tal_bytelen(publictweak), + &tweak); + + kp = secretstuff.bolt12; + + if (secp256k1_keypair_xonly_tweak_add(secp256k1_ctx, + &kp, + tweak.u.u8) != 1) { + return bad_req_fmt(conn, c, msg_in, + "Failed to get tweak key"); + } + } + if (!secp256k1_schnorrsig_sign(secp256k1_ctx, sig.u8, sha.u.u8, - &node_kp, + &kp, NULL, NULL)) { return bad_req_fmt(conn, c, msg_in, "Failed to sign bolt12"); } diff --git a/hsmd/hsmd_wire.csv b/hsmd/hsmd_wire.csv index ee2381175..86be49bb1 100644 --- a/hsmd/hsmd_wire.csv +++ b/hsmd/hsmd_wire.csv @@ -20,6 +20,7 @@ msgdata,hsmd_init,dev_force_channel_secrets_shaseed,?sha256, msgtype,hsmd_init_reply,111 msgdata,hsmd_init_reply,node_id,node_id, msgdata,hsmd_init_reply,bip32,ext_key, +msgdata,hsmd_init_reply,bolt12,pubkey32, # Get a new HSM FD, with the specified capabilities msgtype,hsmd_client_hsmfd,9 @@ -204,6 +205,9 @@ 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 msgtype,hsmd_sign_bolt12_reply,125 msgdata,hsmd_sign_bolt12_reply,sig,bip340sig, diff --git a/hsmd/hsmd_wiregen.c b/hsmd/hsmd_wiregen.c index a5e47b70a..8a0f5e07b 100644 --- a/hsmd/hsmd_wiregen.c +++ b/hsmd/hsmd_wiregen.c @@ -239,17 +239,18 @@ bool fromwire_hsmd_init(const tal_t *ctx, const void *p, struct bip32_key_versio } /* WIRE: HSMD_INIT_REPLY */ -u8 *towire_hsmd_init_reply(const tal_t *ctx, const struct node_id *node_id, const struct ext_key *bip32) +u8 *towire_hsmd_init_reply(const tal_t *ctx, const struct node_id *node_id, const struct ext_key *bip32, const struct pubkey32 *bolt12) { u8 *p = tal_arr(ctx, u8, 0); towire_u16(&p, WIRE_HSMD_INIT_REPLY); towire_node_id(&p, node_id); towire_ext_key(&p, bip32); + towire_pubkey32(&p, bolt12); return memcheck(p, tal_count(p)); } -bool fromwire_hsmd_init_reply(const void *p, struct node_id *node_id, struct ext_key *bip32) +bool fromwire_hsmd_init_reply(const void *p, struct node_id *node_id, struct ext_key *bip32, struct pubkey32 *bolt12) { const u8 *cursor = p; size_t plen = tal_count(p); @@ -258,6 +259,7 @@ bool fromwire_hsmd_init_reply(const void *p, struct node_id *node_id, struct ext return false; fromwire_node_id(&cursor, &plen, node_id); fromwire_ext_key(&cursor, &plen, bip32); + fromwire_pubkey32(&cursor, &plen, bolt12); return cursor != NULL; } @@ -1221,19 +1223,25 @@ bool fromwire_hsmd_get_output_scriptpubkey_reply(const tal_t *ctx, const void *p /* WIRE: HSMD_SIGN_BOLT12 */ /* Sign a bolt12-style merkle hash */ -u8 *towire_hsmd_sign_bolt12(const tal_t *ctx, const wirestring *messagename, const wirestring *fieldname, const struct sha256 *merkleroot) +u8 *towire_hsmd_sign_bolt12(const tal_t *ctx, const wirestring *messagename, const wirestring *fieldname, const struct sha256 *merkleroot, const u8 *publictweak) { + u16 publictweaklen = tal_count(publictweak); u8 *p = tal_arr(ctx, u8, 0); towire_u16(&p, WIRE_HSMD_SIGN_BOLT12); towire_wirestring(&p, messagename); towire_wirestring(&p, fieldname); towire_sha256(&p, merkleroot); + /* This is for invreq payer_id (temporary keys) */ + towire_u16(&p, publictweaklen); + towire_u8_array(&p, publictweak, publictweaklen); return memcheck(p, tal_count(p)); } -bool fromwire_hsmd_sign_bolt12(const tal_t *ctx, const void *p, wirestring **messagename, wirestring **fieldname, struct sha256 *merkleroot) +bool fromwire_hsmd_sign_bolt12(const tal_t *ctx, const void *p, wirestring **messagename, wirestring **fieldname, struct sha256 *merkleroot, u8 **publictweak) { + u16 publictweaklen; + const u8 *cursor = p; size_t plen = tal_count(p); @@ -1242,6 +1250,11 @@ bool fromwire_hsmd_sign_bolt12(const tal_t *ctx, const void *p, wirestring **mes *messagename = fromwire_wirestring(ctx, &cursor, &plen); *fieldname = fromwire_wirestring(ctx, &cursor, &plen); fromwire_sha256(&cursor, &plen, merkleroot); + /* This is for invreq payer_id (temporary keys) */ + publictweaklen = fromwire_u16(&cursor, &plen); + // 2nd case publictweak + *publictweak = publictweaklen ? tal_arr(ctx, u8, publictweaklen) : NULL; + fromwire_u8_array(&cursor, &plen, *publictweak, publictweaklen); return cursor != NULL; } @@ -1265,4 +1278,4 @@ bool fromwire_hsmd_sign_bolt12_reply(const void *p, struct bip340sig *sig) fromwire_bip340sig(&cursor, &plen, sig); return cursor != NULL; } -// SHA256STAMP:cb033b99e13d9bdd06582a34132ba7cd311f4cf074298da1addcdad06b3fdf8f +// SHA256STAMP:bba9aa92e35397eb79f9518bbc058ccac4b51c3e48039f29205703f8bc20111e diff --git a/hsmd/hsmd_wiregen.h b/hsmd/hsmd_wiregen.h index 17ec54d3e..a457e5bc1 100644 --- a/hsmd/hsmd_wiregen.h +++ b/hsmd/hsmd_wiregen.h @@ -104,8 +104,8 @@ u8 *towire_hsmd_init(const tal_t *ctx, const struct bip32_key_version *bip32_key bool fromwire_hsmd_init(const tal_t *ctx, const void *p, struct bip32_key_version *bip32_key_version, const struct chainparams **chainparams, struct secret **hsm_encryption_key, struct privkey **dev_force_privkey, struct secret **dev_force_bip32_seed, struct secrets **dev_force_channel_secrets, struct sha256 **dev_force_channel_secrets_shaseed); /* WIRE: HSMD_INIT_REPLY */ -u8 *towire_hsmd_init_reply(const tal_t *ctx, const struct node_id *node_id, const struct ext_key *bip32); -bool fromwire_hsmd_init_reply(const void *p, struct node_id *node_id, struct ext_key *bip32); +u8 *towire_hsmd_init_reply(const tal_t *ctx, const struct node_id *node_id, const struct ext_key *bip32, const struct pubkey32 *bolt12); +bool fromwire_hsmd_init_reply(const void *p, struct node_id *node_id, struct ext_key *bip32, struct pubkey32 *bolt12); /* WIRE: HSMD_CLIENT_HSMFD */ /* Get a new HSM FD */ @@ -274,8 +274,8 @@ bool fromwire_hsmd_get_output_scriptpubkey_reply(const tal_t *ctx, const void *p /* WIRE: HSMD_SIGN_BOLT12 */ /* Sign a bolt12-style merkle hash */ -u8 *towire_hsmd_sign_bolt12(const tal_t *ctx, const wirestring *messagename, const wirestring *fieldname, const struct sha256 *merkleroot); -bool fromwire_hsmd_sign_bolt12(const tal_t *ctx, const void *p, wirestring **messagename, wirestring **fieldname, struct sha256 *merkleroot); +u8 *towire_hsmd_sign_bolt12(const tal_t *ctx, const wirestring *messagename, const wirestring *fieldname, const struct sha256 *merkleroot, const u8 *publictweak); +bool fromwire_hsmd_sign_bolt12(const tal_t *ctx, const void *p, wirestring **messagename, wirestring **fieldname, struct sha256 *merkleroot, u8 **publictweak); /* WIRE: HSMD_SIGN_BOLT12_REPLY */ u8 *towire_hsmd_sign_bolt12_reply(const tal_t *ctx, const struct bip340sig *sig); @@ -283,4 +283,4 @@ bool fromwire_hsmd_sign_bolt12_reply(const void *p, struct bip340sig *sig); #endif /* LIGHTNING_HSMD_HSMD_WIREGEN_H */ -// SHA256STAMP:cb033b99e13d9bdd06582a34132ba7cd311f4cf074298da1addcdad06b3fdf8f +// SHA256STAMP:bba9aa92e35397eb79f9518bbc058ccac4b51c3e48039f29205703f8bc20111e diff --git a/lightningd/hsm_control.c b/lightningd/hsm_control.c index d3c144c3f..15e5eb388 100644 --- a/lightningd/hsm_control.c +++ b/lightningd/hsm_control.c @@ -125,7 +125,8 @@ struct ext_key *hsm_init(struct lightningd *ld) bip32_base = tal(ld, struct ext_key); msg = wire_sync_read(tmpctx, ld->hsm_fd); if (!fromwire_hsmd_init_reply(msg, - &ld->id, bip32_base)) { + &ld->id, bip32_base, + &ld->bolt12_base)) { if (ld->config.keypass) errx(1, "Wrong password for encrypted hsm_secret."); errx(1, "HSM did not give init reply"); diff --git a/lightningd/invoice.c b/lightningd/invoice.c index 0098b6f73..dc15f2f90 100644 --- a/lightningd/invoice.c +++ b/lightningd/invoice.c @@ -442,7 +442,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); + msg = towire_hsmd_sign_bolt12(NULL, "invoice", "signature", &merkle, NULL); if (!wire_sync_write(ld->hsm_fd, take(msg))) fatal("Could not write to HSM: %s", strerror(errno)); diff --git a/lightningd/lightningd.h b/lightningd/lightningd.h index 9e7f2de4c..9f83f27ac 100644 --- a/lightningd/lightningd.h +++ b/lightningd/lightningd.h @@ -111,6 +111,9 @@ struct lightningd { /* This is us. */ struct node_id id; + /* The public base for our payer_id keys */ + struct pubkey32 bolt12_base; + /* Feature set we offer. */ struct feature_set *our_features; diff --git a/lightningd/offer.c b/lightningd/offer.c index ca3843918..f6f751ce4 100644 --- a/lightningd/offer.c +++ b/lightningd/offer.c @@ -49,7 +49,7 @@ static void hsm_sign_b12_offer(struct lightningd *ld, { u8 *msg; - msg = towire_hsmd_sign_bolt12(NULL, "offer", "signature", merkle); + msg = towire_hsmd_sign_bolt12(NULL, "offer", "signature", merkle, NULL); if (!wire_sync_write(ld->hsm_fd, take(msg))) fatal("Could not write to HSM: %s", strerror(errno)); diff --git a/lightningd/test/run-invoice-select-inchan.c b/lightningd/test/run-invoice-select-inchan.c index 8c127e734..4e50c776a 100644 --- a/lightningd/test/run-invoice-select-inchan.c +++ b/lightningd/test/run-invoice-select-inchan.c @@ -569,7 +569,7 @@ u8 *towire_errorfmt(const tal_t *ctx UNNEEDED, u8 *towire_gossipd_get_incoming_channels(const tal_t *ctx UNNEEDED) { fprintf(stderr, "towire_gossipd_get_incoming_channels 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) +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) { 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)