hsmd/libhsmd: change hsmd_init to have secret_data and secret_len
What changed, and why it matters
This is a preparatory code change for Core Lightning's HSM (the component that holds private keys). It changes an internal function so it can accept either a 32-byte or 64-byte root secret, keeping the existing 32-byte behavior intact. The commit itself does not change how keys are actually derived or fix any known vulnerability; it is plumbing for a future feature (BIP86 wallet addresses).
No immediate security action required. Treat as normal feature/refactor commit. Review the follow-up commits that actually switch wallet address derivation to the 64-byte seed to ensure correct handling of seed length and legacy compatibility.
Security signals we found
Refactor of HSM root secret handling with variable-length input
Use of sodium_mlock on the new variable-length seed
Use of notleak/tal_dup_arr to prevent memory-leak scanner false positives
Legacy 32-byte mirror retained for backward compatibility
No functional change to key derivation in this commit
Evidence from the diff
The commit refactors hsmd_init() to take a length-aware secret (const u8 *secret_data, size_t secret_len) instead of a fixed struct secret. It stores the full variable-length seed in a new notleak/tal_dup_arr allocation and mlocks it, while also copying the first 32 bytes into the legacy hsm_secret field so existing code paths continue to work. The actual BIP32 derivation still uses the 32-byte seed via a local bip32_seed buffer; the follow-up work to use the 64-byte seed for wallet address derivation is explicitly noted as not yet done.
Changed components
hsmd/hsmd.chsmd/libhsmd.chsmd/libhsmd.hInspect captured patch +21 / −9
diff --git a/hsmd/hsmd.c b/hsmd/hsmd.c
index 5064083..0d7de8b 100644
--- a/hsmd/hsmd.c
+++ b/hsmd/hsmd.c
@@ -551,7 +551,9 @@ static struct io_plan *init_hsm(struct io_conn *conn,
/* This was tallocated off NULL, and memleak complains if we don't free it */
tal_free(tlvs);
- return req_reply(conn, c, hsmd_init(hsm_secret.secret, hsmd_mutual_version,
+ return req_reply(conn, c, hsmd_init(hsm_secret_bytes(&hsm_secret),
+ hsm_secret_size(&hsm_secret),
+ hsmd_mutual_version,
bip32_key_version));
}
diff --git a/hsmd/libhsmd.c b/hsmd/libhsmd.c
index 9fde4d1..2adeed1 100644
--- a/hsmd/libhsmd.c
+++ b/hsmd/libhsmd.c
@@ -8,6 +8,7 @@
#include <common/hash_u5.h>
#include <common/key_derive.h>
#include <common/lease_rates.h>
+#include <common/memleak.h>
#include <common/utils.h>
#include <hsmd/libhsmd.h>
#include <hsmd/permissions.h>
@@ -25,11 +26,17 @@ struct privkey *dev_force_privkey;
/* If they specify --dev-force-bip32-seed it ends up in here. */
struct secret *dev_force_bip32_seed;
-/*~ Nobody will ever find it here! hsm_secret is our root secret, the bip32
+/*~ Nobody will ever find it here! bip32_seed is our root secret, the bip32
* tree, bolt12 payer_id keys and derived_secret are derived from that, and
* cached here. */
struct {
+ /* keep for legacy callers that read 32B directly today */
struct secret hsm_secret;
+
+ /* new: full root bytes we received (32 or 64) */
+ u8 *bip32_seed;
+ size_t bip32_seed_len;
+
struct ext_key bip32;
struct secret bolt12;
struct secret derived_secret;
@@ -2334,7 +2341,7 @@ u8 *hsmd_handle_client_message(const tal_t *ctx, struct hsmd_client *client,
return hsmd_status_bad_request(client, msg, "Unknown request");
}
-u8 *hsmd_init(struct secret hsm_secret, const u64 hsmd_version,
+u8 *hsmd_init(const u8 *secret_data, size_t secret_len, const u64 hsmd_version,
struct bip32_key_version bip32_key_version)
{
u8 bip32_seed[BIP32_ENTROPY_LEN_256];
@@ -2356,10 +2363,13 @@ u8 *hsmd_init(struct secret hsm_secret, const u64 hsmd_version,
};
u32 *caps;
- /*~ Don't swap this. */
- sodium_mlock(secretstuff.hsm_secret.data,
- sizeof(secretstuff.hsm_secret.data));
- memcpy(secretstuff.hsm_secret.data, hsm_secret.data, sizeof(hsm_secret.data));
+ /* new: keep the full 32/64B root */
+ secretstuff.bip32_seed_len = secret_len;
+ secretstuff.bip32_seed = notleak(tal_dup_arr(NULL, u8, secret_data, secret_len, 0));
+ sodium_mlock(secretstuff.bip32_seed, secret_len);
+
+ /* legacy mirror: first 32 bytes for existing code paths */
+ memcpy(secretstuff.hsm_secret.data, secret_data, 32);
assert(bip32_key_version.bip32_pubkey_version == BIP32_VER_MAIN_PUBLIC
|| bip32_key_version.bip32_pubkey_version == BIP32_VER_TEST_PUBLIC);
diff --git a/hsmd/libhsmd.h b/hsmd/libhsmd.h
index 652340e..35d6904 100644
--- a/hsmd/libhsmd.h
+++ b/hsmd/libhsmd.h
@@ -38,7 +38,7 @@ struct hsmd_client {
/* Given the (unencrypted) base secret, intialize all derived secrets.
*
* While we ensure that the memory the internal secrets are stored in
- * is secure (mlock), the caller must make sure that the `hsm_secret`
+ * is secure (mlock), the caller must make sure that the `secret_data`
* argument is handled securely before this call to avoid potential
* issues. The function copies the secret, so the caller can free the
* secret after the call.
@@ -46,7 +46,7 @@ struct hsmd_client {
* Returns the `hsmd_init_reply` with the information required by
* `lightningd`.
*/
-u8 *hsmd_init(struct secret hsm_secret, const u64 hsmd_version,
+u8 *hsmd_init(const u8 *secret_data, size_t secret_len, const u64 hsmd_version,
struct bip32_key_version bip32_key_version);
struct hsmd_client *hsmd_client_new_main(const tal_t *ctx, u64 capabilities,
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.