lightningd: store base and derive pubkeys locally

RIP to this commit there's a good chance a lot of this code doesn't even make this into the final PR. Pour one out for the fallen lines of code.

This commit is doing the rest of the derivation. There was a significant overlap between the bip32_pubkey derivation and the bip86_pubkey derivation so that has been refactored in one place.
This commit is contained in:
Sangbida Chaudhuri
2025-10-24 13:57:46 +10:30
committed by Rusty Russell
parent 63001745ca
commit 1665665271
7 changed files with 84 additions and 3 deletions

View File

@@ -140,7 +140,6 @@ bool hsmd_check_client_capabilities(struct hsmd_client *client,
case WIRE_HSMD_DERIVE_BIP86_KEY: case WIRE_HSMD_DERIVE_BIP86_KEY:
case WIRE_HSMD_CHECK_BIP86_PUBKEY: case WIRE_HSMD_CHECK_BIP86_PUBKEY:
return (client->capabilities & HSM_PERM_DERIVE_BIP86_KEY) != 0;
case WIRE_HSMD_INIT: case WIRE_HSMD_INIT:
case WIRE_HSMD_DEV_PREINIT: case WIRE_HSMD_DEV_PREINIT:
case WIRE_HSMD_NEW_CHANNEL: case WIRE_HSMD_NEW_CHANNEL:

View File

@@ -11,7 +11,6 @@
#define HSM_PERM_SIGN_WILL_FUND_OFFER 64 #define HSM_PERM_SIGN_WILL_FUND_OFFER 64
#define HSM_PERM_SIGN_SPLICE_TX 128 #define HSM_PERM_SIGN_SPLICE_TX 128
#define HSM_PERM_LOCK_OUTPOINT 256 #define HSM_PERM_LOCK_OUTPOINT 256
#define HSM_PERM_DERIVE_BIP86_KEY 512
#define HSM_PERM_MASTER 1024 #define HSM_PERM_MASTER 1024
#endif /* LIGHTNING_HSMD_PERMISSIONS_H */ #endif /* LIGHTNING_HSMD_PERMISSIONS_H */

View File

@@ -188,6 +188,19 @@ struct ext_key *hsm_init(struct lightningd *ld)
fatal("--experimental-splicing needs HSM capable of signing splices!"); fatal("--experimental-splicing needs HSM capable of signing splices!");
} }
/* Check if BIP86 derivation is requested and supported */
if (ld->use_bip86_derivation) {
/* Get BIP86 base key from HSM */
ld->bip86_base = tal(ld, struct ext_key);
msg = towire_hsmd_derive_bip86_key(NULL, 0, false);
const u8 *reply = hsm_sync_req(tmpctx, ld, take(msg));
if (!fromwire_hsmd_derive_bip86_key_reply(reply, ld->bip86_base)) {
errx(EXITCODE_HSM_GENERIC_ERROR, "Failed to get BIP86 base key from HSM");
}
} else {
ld->bip86_base = NULL;
}
/* This is equivalent to makesecret("bolt12-invoice-base") */ /* This is equivalent to makesecret("bolt12-invoice-base") */
msg = towire_hsmd_derive_secret(NULL, tal_dup_arr(tmpctx, u8, msg = towire_hsmd_derive_secret(NULL, tal_dup_arr(tmpctx, u8,
(const u8 *)BOLT12_ID_BASE_STRING, (const u8 *)BOLT12_ID_BASE_STRING,
@@ -213,6 +226,11 @@ struct ext_key *hsm_init(struct lightningd *ld)
return bip32_base; return bip32_base;
} }
/*~ There was a nasty LND bug report where the user issued an address which it
* couldn't spend, presumably due to a bitflip. We check every address using our
* hsm, to be sure it's valid. Expensive, but not as expensive as losing BTC! */
/* Verify a derived public key with the HSM */
/*~ There was a nasty LND bug report where the user issued an address which it /*~ There was a nasty LND bug report where the user issued an address which it
* couldn't spend, presumably due to a bitflip. We check every address using our * couldn't spend, presumably due to a bitflip. We check every address using our
* hsm, to be sure it's valid. Expensive, but not as expensive as losing BTC! */ * hsm, to be sure it's valid. Expensive, but not as expensive as losing BTC! */
@@ -222,7 +240,7 @@ void bip32_pubkey(struct lightningd *ld, struct pubkey *pubkey, u32 index)
struct ext_key ext; struct ext_key ext;
if (index >= BIP32_INITIAL_HARDENED_CHILD) if (index >= BIP32_INITIAL_HARDENED_CHILD)
fatal("Can't derive keu %u (too large!)", index); fatal("Can't derive key %u (too large!)", index);
if (bip32_key_from_parent(ld->bip32_base, index, flags, &ext) != WALLY_OK) if (bip32_key_from_parent(ld->bip32_base, index, flags, &ext) != WALLY_OK)
fatal("Can't derive key %u", index); fatal("Can't derive key %u", index);
@@ -238,12 +256,50 @@ void bip32_pubkey(struct lightningd *ld, struct pubkey *pubkey, u32 index)
msg = hsm_sync_req(tmpctx, ld, take(msg)); msg = hsm_sync_req(tmpctx, ld, take(msg));
if (!fromwire_hsmd_check_pubkey_reply(msg, &ok)) if (!fromwire_hsmd_check_pubkey_reply(msg, &ok))
fatal("Invalid check_pubkey_reply from hsm"); fatal("Invalid check_pubkey_reply from hsm");
if (!ok) if (!ok)
fatal("HSM said key derivation of %u != %s", fatal("HSM said key derivation of %u != %s",
index, fmt_pubkey(tmpctx, pubkey)); index, fmt_pubkey(tmpctx, pubkey));
} }
} }
/* Derive BIP86 public key from the base key */
void bip86_pubkey(struct lightningd *ld, struct pubkey *pubkey, u32 index)
{
const uint32_t flags = BIP32_FLAG_KEY_PUBLIC | BIP32_FLAG_SKIP_HASH;
struct ext_key ext;
u32 path[2];
if (index >= BIP32_INITIAL_HARDENED_CHILD)
fatal("Can't derive key %u (too large!)", index);
/* BIP86 path: m/86'/0'/0'/0/index */
path[0] = 0; /* change (0 for receive) */
path[1] = index; /* address_index */
assert(ld->bip86_base != NULL);
if (bip32_key_from_parent_path(ld->bip86_base, path, 2, flags, &ext) != WALLY_OK)
fatal("Can't derive key %u", index);
if (!secp256k1_ec_pubkey_parse(secp256k1_ctx, &pubkey->pubkey,
ext.pub_key, sizeof(ext.pub_key)))
fatal("Can't parse derived key %u", index);
/* Don't assume hsmd supports it! */
if (hsm_capable(ld, WIRE_HSMD_CHECK_BIP86_PUBKEY)) {
bool ok;
const u8 *msg = towire_hsmd_check_bip86_pubkey(NULL, index, pubkey);
msg = hsm_sync_req(tmpctx, ld, take(msg));
if (!fromwire_hsmd_check_bip86_pubkey_reply(msg, &ok))
fatal("Invalid check_bip86_pubkey_reply from hsm");
if (!ok)
fatal("HSM said BIP86 key derivation of %u != %s",
index, fmt_pubkey(tmpctx, pubkey));
}
}
const u8 *hsm_sync_req(const tal_t *ctx, struct lightningd *ld, const u8 *msg) const u8 *hsm_sync_req(const tal_t *ctx, struct lightningd *ld, const u8 *msg)
{ {
int type = fromwire_peektype(msg); int type = fromwire_peektype(msg);

View File

@@ -29,5 +29,6 @@ const u8 *hsm_sync_req(const tal_t *ctx,
/* Get (and check!) a bip32 derived pubkey */ /* Get (and check!) a bip32 derived pubkey */
void bip32_pubkey(struct lightningd *ld, struct pubkey *pubkey, u32 index); void bip32_pubkey(struct lightningd *ld, struct pubkey *pubkey, u32 index);
void bip86_pubkey(struct lightningd *ld, struct pubkey *pubkey, u32 index);
#endif /* LIGHTNING_LIGHTNINGD_HSM_CONTROL_H */ #endif /* LIGHTNING_LIGHTNINGD_HSM_CONTROL_H */

View File

@@ -236,6 +236,8 @@ struct lightningd {
/* Derive all our keys from here (see bip32_pubkey) */ /* Derive all our keys from here (see bip32_pubkey) */
struct ext_key *bip32_base; struct ext_key *bip32_base;
/* Derive all our BIP86 keys from here */
struct ext_key *bip86_base;
struct wallet *wallet; struct wallet *wallet;
/* Outstanding waitsendpay commands. */ /* Outstanding waitsendpay commands. */
@@ -387,6 +389,9 @@ struct lightningd {
/* HSM passphrase for any format that needs it */ /* HSM passphrase for any format that needs it */
char *hsm_passphrase; char *hsm_passphrase;
/* Enable BIP86 derivation for mnemonic-based HSM secrets */
bool use_bip86_derivation;
/* What (additional) messages the HSM accepts */ /* What (additional) messages the HSM accepts */
u32 *hsm_capabilities; u32 *hsm_capabilities;

View File

@@ -632,6 +632,12 @@ static char *opt_set_hsm_passphrase(struct lightningd *ld)
return read_hsm_passphrase(ld); return read_hsm_passphrase(ld);
} }
static char *opt_set_bip86_derivation(struct lightningd *ld)
{
ld->use_bip86_derivation = true;
return NULL;
}
static char *opt_force_privkey(const char *optarg, struct lightningd *ld) static char *opt_force_privkey(const char *optarg, struct lightningd *ld)
{ {
tal_free(ld->dev_force_privkey); tal_free(ld->dev_force_privkey);
@@ -1553,6 +1559,9 @@ static void register_opts(struct lightningd *ld)
opt_register_noarg("--hsm-passphrase", opt_set_hsm_passphrase, ld, opt_register_noarg("--hsm-passphrase", opt_set_hsm_passphrase, ld,
"Prompt for passphrase for encrypted hsm_secret (replaces --encrypted-hsm)"); "Prompt for passphrase for encrypted hsm_secret (replaces --encrypted-hsm)");
opt_register_noarg("--use-bip86-derivation", opt_set_bip86_derivation, ld,
"Use BIP86 derivation for mnemonic-based HSM secrets (experimental)");
opt_register_arg("--rpc-file-mode", &opt_set_mode, &opt_show_mode, opt_register_arg("--rpc-file-mode", &opt_set_mode, &opt_show_mode,
&ld->rpc_filemode, &ld->rpc_filemode,
"Set the file mode (permissions) for the " "Set the file mode (permissions) for the "

View File

@@ -315,6 +315,9 @@ bool fromwire_dualopend_dev_memleak_reply(const void *p UNNEEDED, bool *leak UNN
/* Generated stub for fromwire_gossipd_addgossip_reply */ /* Generated stub for fromwire_gossipd_addgossip_reply */
bool fromwire_gossipd_addgossip_reply(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, wirestring **err UNNEEDED) bool fromwire_gossipd_addgossip_reply(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, wirestring **err UNNEEDED)
{ fprintf(stderr, "fromwire_gossipd_addgossip_reply called!\n"); abort(); } { fprintf(stderr, "fromwire_gossipd_addgossip_reply called!\n"); abort(); }
/* Generated stub for fromwire_hsmd_check_bip86_pubkey_reply */
bool fromwire_hsmd_check_bip86_pubkey_reply(const void *p UNNEEDED, bool *ok UNNEEDED)
{ fprintf(stderr, "fromwire_hsmd_check_bip86_pubkey_reply called!\n"); abort(); }
/* Generated stub for fromwire_hsmd_check_pubkey_reply */ /* Generated stub for fromwire_hsmd_check_pubkey_reply */
bool fromwire_hsmd_check_pubkey_reply(const void *p UNNEEDED, bool *ok UNNEEDED) bool fromwire_hsmd_check_pubkey_reply(const void *p UNNEEDED, bool *ok UNNEEDED)
{ fprintf(stderr, "fromwire_hsmd_check_pubkey_reply called!\n"); abort(); } { fprintf(stderr, "fromwire_hsmd_check_pubkey_reply called!\n"); abort(); }
@@ -324,6 +327,9 @@ bool fromwire_hsmd_client_hsmfd_reply(const void *p UNNEEDED)
/* Generated stub for fromwire_hsmd_cupdate_sig_reply */ /* Generated stub for fromwire_hsmd_cupdate_sig_reply */
bool fromwire_hsmd_cupdate_sig_reply(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, u8 **cu UNNEEDED) bool fromwire_hsmd_cupdate_sig_reply(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, u8 **cu UNNEEDED)
{ fprintf(stderr, "fromwire_hsmd_cupdate_sig_reply called!\n"); abort(); } { fprintf(stderr, "fromwire_hsmd_cupdate_sig_reply called!\n"); abort(); }
/* Generated stub for fromwire_hsmd_derive_bip86_key_reply */
bool fromwire_hsmd_derive_bip86_key_reply(const void *p UNNEEDED, struct ext_key *bip86_base UNNEEDED)
{ fprintf(stderr, "fromwire_hsmd_derive_bip86_key_reply called!\n"); abort(); }
/* Generated stub for fromwire_hsmd_derive_secret_reply */ /* Generated stub for fromwire_hsmd_derive_secret_reply */
bool fromwire_hsmd_derive_secret_reply(const void *p UNNEEDED, struct secret *secret UNNEEDED) bool fromwire_hsmd_derive_secret_reply(const void *p UNNEEDED, struct secret *secret UNNEEDED)
{ fprintf(stderr, "fromwire_hsmd_derive_secret_reply called!\n"); abort(); } { fprintf(stderr, "fromwire_hsmd_derive_secret_reply called!\n"); abort(); }
@@ -693,6 +699,9 @@ u8 *towire_dualopend_dev_memleak(const tal_t *ctx UNNEEDED)
/* Generated stub for towire_gossipd_addgossip */ /* Generated stub for towire_gossipd_addgossip */
u8 *towire_gossipd_addgossip(const tal_t *ctx UNNEEDED, const u8 *msg UNNEEDED, struct amount_sat *known_channel UNNEEDED) u8 *towire_gossipd_addgossip(const tal_t *ctx UNNEEDED, const u8 *msg UNNEEDED, struct amount_sat *known_channel UNNEEDED)
{ fprintf(stderr, "towire_gossipd_addgossip called!\n"); abort(); } { fprintf(stderr, "towire_gossipd_addgossip called!\n"); abort(); }
/* Generated stub for towire_hsmd_check_bip86_pubkey */
u8 *towire_hsmd_check_bip86_pubkey(const tal_t *ctx UNNEEDED, u32 index UNNEEDED, const struct pubkey *pubkey UNNEEDED)
{ fprintf(stderr, "towire_hsmd_check_bip86_pubkey called!\n"); abort(); }
/* Generated stub for towire_hsmd_check_pubkey */ /* Generated stub for towire_hsmd_check_pubkey */
u8 *towire_hsmd_check_pubkey(const tal_t *ctx UNNEEDED, u32 index UNNEEDED, const struct pubkey *pubkey UNNEEDED) u8 *towire_hsmd_check_pubkey(const tal_t *ctx UNNEEDED, u32 index UNNEEDED, const struct pubkey *pubkey UNNEEDED)
{ fprintf(stderr, "towire_hsmd_check_pubkey called!\n"); abort(); } { fprintf(stderr, "towire_hsmd_check_pubkey called!\n"); abort(); }
@@ -702,6 +711,9 @@ u8 *towire_hsmd_client_hsmfd(const tal_t *ctx UNNEEDED, const struct node_id *id
/* Generated stub for towire_hsmd_cupdate_sig_req */ /* Generated stub for towire_hsmd_cupdate_sig_req */
u8 *towire_hsmd_cupdate_sig_req(const tal_t *ctx UNNEEDED, const u8 *cu UNNEEDED) u8 *towire_hsmd_cupdate_sig_req(const tal_t *ctx UNNEEDED, const u8 *cu UNNEEDED)
{ fprintf(stderr, "towire_hsmd_cupdate_sig_req called!\n"); abort(); } { fprintf(stderr, "towire_hsmd_cupdate_sig_req called!\n"); abort(); }
/* Generated stub for towire_hsmd_derive_bip86_key */
u8 *towire_hsmd_derive_bip86_key(const tal_t *ctx UNNEEDED, u32 index UNNEEDED, bool is_change UNNEEDED)
{ fprintf(stderr, "towire_hsmd_derive_bip86_key called!\n"); abort(); }
/* Generated stub for towire_hsmd_derive_secret */ /* Generated stub for towire_hsmd_derive_secret */
u8 *towire_hsmd_derive_secret(const tal_t *ctx UNNEEDED, const u8 *info UNNEEDED) u8 *towire_hsmd_derive_secret(const tal_t *ctx UNNEEDED, const u8 *info UNNEEDED)
{ fprintf(stderr, "towire_hsmd_derive_secret called!\n"); abort(); } { fprintf(stderr, "towire_hsmd_derive_secret called!\n"); abort(); }