common: refactor hsm_secret to {secret_data,len,type}
What changed, and why it matters
This commit is a code refactor in Core Lightning's wallet secret handling. It changes how the HSM (Hardware Security Module-like) secret is stored internally so it can hold either the traditional 32-byte secret or a full 64-byte seed derived from a BIP39 mnemonic. It keeps the old 32-byte field for backward compatibility and adds helper functions to read the secret. There is no security fix or vulnerability being patched here.
No security action required. Treat as normal feature/refactoring work. Review follow-up commits to ensure the new accessors are consistently used and that the legacy 32-byte field is eventually removed to avoid confusion.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit refactors struct hsm_secret to add a new variable-length secret_data field alongside the legacy fixed 32-byte struct secret secret. For plain and encrypted secrets, secret_data is populated with 32 bytes. For mnemonic-derived secrets, it stores the full 64-byte BIP32 seed, while the legacy field continues to receive only the first 32 bytes. Two accessors, hsm_secret_bytes() and hsm_secret_size(), are introduced to abstract access to the new field with fallback to the legacy field. This is preparatory work for BIP86 derivation support.
Changed components
common/hsm_secret.ccommon/hsm_secret.hInspect captured patch +46 / −3
diff --git a/common/hsm_secret.c b/common/hsm_secret.c
index a7d2ec2..f377d91 100644
--- a/common/hsm_secret.c
+++ b/common/hsm_secret.c
@@ -13,6 +13,10 @@
#include <unistd.h>
#include <wally_bip39.h>
+/* HSM secret size constants */
+#define HSM_SECRET_PLAIN_SIZE 32
+#define HSM_SECRET_MNEMONIC_SIZE 64
+
/* Length of the encrypted hsm secret header. */
#define HS_HEADER_LEN crypto_secretstream_xchacha20poly1305_HEADERBYTES
/* From libsodium: "The ciphertext length is guaranteed to always be message
@@ -219,6 +223,11 @@ static struct hsm_secret *extract_plain_secret(const tal_t *ctx,
assert(len == sizeof(hsms->secret));
hsms->type = HSM_SECRET_PLAIN;
hsms->mnemonic = NULL;
+
+ /* Allocate and populate secret_data (new field) */
+ hsms->secret_data = tal_dup_arr(hsms, u8, hsm_secret, HSM_SECRET_PLAIN_SIZE, 0);
+
+ /* Also populate legacy secret field for compatibility */
memcpy(&hsms->secret, hsm_secret, sizeof(hsms->secret));
*err = HSM_SECRET_OK;
@@ -261,6 +270,9 @@ static struct hsm_secret *extract_encrypted_secret(const tal_t *ctx,
return tal_free(hsms);
}
+ /* Allocate and populate secret_data (new field) */
+ hsms->secret_data = tal_dup_arr(hsms, u8, hsms->secret.data, HSM_SECRET_PLAIN_SIZE, 0);
+
hsms->type = HSM_SECRET_ENCRYPTED;
hsms->mnemonic = NULL;
@@ -327,7 +339,10 @@ static struct hsm_secret *extract_mnemonic_secret(const tal_t *ctx,
return tal_free(hsms);
}
- /* We only use the first 32 bytes for the hsm_secret */
+ /* Allocate and populate secret_data with full 64-byte seed */
+ hsms->secret_data = tal_dup_arr(hsms, u8, bip32_seed.seed, sizeof(bip32_seed.seed), 0);
+
+ /* Also populate legacy secret field with first 32 bytes for compatibility */
memcpy(hsms->secret.data, bip32_seed.seed, sizeof(hsms->secret.data));
*err = HSM_SECRET_OK;
@@ -528,3 +543,17 @@ u8 *grab_file_contents(const tal_t *ctx, const char *filename, size_t *len)
return contents;
}
+
+const u8 *hsm_secret_bytes(const struct hsm_secret *hsm)
+{
+ if (hsm->secret_data)
+ return hsm->secret_data;
+ return hsm->secret.data;
+}
+
+size_t hsm_secret_size(const struct hsm_secret *hsm)
+{
+ if (hsm->secret_data)
+ return tal_bytelen(hsm->secret_data);
+ return sizeof(hsm->secret);
+}
diff --git a/common/hsm_secret.h b/common/hsm_secret.h
index 4fca3a4..d1d1f56 100644
--- a/common/hsm_secret.h
+++ b/common/hsm_secret.h
@@ -3,6 +3,7 @@
#include "config.h"
#include <bitcoin/privkey.h>
#include <ccan/crypto/sha256/sha256.h>
+#include <ccan/short_types/short_types.h>
#include <ccan/tal/tal.h>
#include <sodium.h>
#include <sys/types.h>
@@ -39,10 +40,23 @@ enum hsm_secret_error {
*/
struct hsm_secret {
enum hsm_secret_type type;
- struct secret secret;
- const char *mnemonic; /* NULL if not derived from mnemonic */
+ u8 *secret_data; /* Variable length: 32 bytes (legacy) or 64 bytes (mnemonic) */
+ struct secret secret; /* Legacy 32-byte field for compatibility */
+ const char *mnemonic; /* NULL if not derived from mnemonic */
};
+/**
+ * Get the secret bytes from an hsm_secret.
+ * Returns secret_data if available, otherwise falls back to legacy secret.data.
+ */
+const u8 *hsm_secret_bytes(const struct hsm_secret *hsm);
+
+/**
+ * Get the secret size from an hsm_secret.
+ * Returns tal_bytelen of secret_data if available, otherwise 32 bytes for legacy.
+ */
+size_t hsm_secret_size(const struct hsm_secret *hsm);
+
/**
* Checks whether the hsm_secret data requires a passphrase to decrypt.
* Handles legacy, encrypted, and mnemonic-based formats.
Why this scored 12/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.