common: add unified HSM secret handling module
What changed, and why it matters
This commit adds a new internal module for handling the secret key file used by Core Lightning's hardware security module (HSM). It introduces support for reading the existing plain and encrypted formats, plus new formats based on BIP39 word lists (mnemonics) with optional passphrases. The code uses modern password hashing (Argon2) and authenticated encryption (libsodium secretstream). There is no direct evidence in the commit that this fixes a known security vulnerability; it appears to be a feature/refactoring change to support new wallet backup formats.
No immediate security action is required. Treat this as a normal feature/refactoring commit. Reviewers should verify that downstream commits using this module correctly validate passphrases, handle memory locking failures, and do not introduce side-channel or format-confusion issues when the new mnemonic formats are enabled.
Security signals we found
Adds new cryptographic secret-handling module
Uses Argon2id and libsodium secretstream for encryption
Introduces BIP39 mnemonic parsing and seed derivation
Memory-locks secrets and clears them after use
No explicit security fix or CVE reference in commit message
Evidence from the diff
The patch creates common/hsm_secret.c and common/hsm_secret.h and adds the C file to common/Makefile. It provides a unified API to parse hsm_secret files, supporting four formats: legacy 32-byte plaintext, legacy 73-byte encrypted (xchacha20poly1305 secretstream), mnemonic with no passphrase (32 zero bytes + mnemonic), and mnemonic with passphrase (32-byte seed hash + mnemonic). Key functions include detect_hsm_secret_type(), extract_hsm_secret(), get_encryption_key() (Argon2id), encrypt_legacy_hsm_secret(), read_stdin_pass(), read_stdin_mnemonic(), and derive_seed_hash(). The code locks sensitive memory with sodium_mlock and clears secrets on destruction. No callers are added in this commit, so it is a foundational module only.
Changed components
common/hsm_secret.ccommon/hsm_secret.hcommon/MakefileInspect captured patch +614 / −0
diff --git a/common/Makefile b/common/Makefile
index cf203bf..5b85385 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -46,6 +46,7 @@ COMMON_SRC_NOGEN := \
common/hmac.c \
common/hsm_capable.c \
common/hsm_encryption.c \
+ common/hsm_secret.c \
common/htlc_state.c \
common/htlc_trim.c \
common/htlc_tx.c \
diff --git a/common/hsm_secret.c b/common/hsm_secret.c
new file mode 100644
index 0000000..e7678dc
--- /dev/null
+++ b/common/hsm_secret.c
@@ -0,0 +1,461 @@
+#include "config.h"
+#include <assert.h>
+#include <ccan/mem/mem.h>
+#include <ccan/tal/str/str.h>
+#include <common/errcode.h>
+#include <common/hsm_secret.h>
+#include <common/utils.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <termios.h>
+#include <unistd.h>
+#include <wally_bip39.h>
+
+/* 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
+ * length + ABYTES" */
+#define HS_CIPHERTEXT_LEN \
+ (sizeof(struct secret) + crypto_secretstream_xchacha20poly1305_ABYTES)
+/* Total length of an encrypted hsm_secret */
+#define ENCRYPTED_HSM_SECRET_LEN (HS_HEADER_LEN + HS_CIPHERTEXT_LEN)
+#define PASSPHRASE_HASH_LEN 32
+#define HSM_SECRET_PLAIN_SIZE 32
+
+void destroy_secret(struct secret *secret)
+{
+ sodium_munlock(secret->data, sizeof(secret->data));
+}
+
+/* Helper function to validate a mnemonic string */
+static bool validate_mnemonic(const char *mnemonic, enum hsm_secret_error *err)
+{
+ struct words *words;
+
+ if (bip39_get_wordlist("en", &words) != WALLY_OK) {
+ abort();
+ }
+
+ if (bip39_mnemonic_validate(words, mnemonic) != WALLY_OK) {
+ *err = HSM_SECRET_ERR_INVALID_MNEMONIC;
+ return false;
+ }
+
+ return true;
+}
+
+struct secret *get_encryption_key(const tal_t *ctx, const char *passphrase)
+{
+ struct secret *secret = tal(ctx, struct secret);
+ const u8 salt[16] = "c-lightning\0\0\0\0\0";
+
+ /* Check bounds. */
+ if (strlen(passphrase) < crypto_pwhash_argon2id_PASSWD_MIN) {
+ return tal_free(secret);
+ } else if (strlen(passphrase) > crypto_pwhash_argon2id_PASSWD_MAX) {
+ return tal_free(secret);
+ }
+
+ /* Don't swap the encryption key ! */
+ if (sodium_mlock(secret->data, sizeof(secret->data)) != 0)
+ return tal_free(secret);
+ tal_add_destructor(secret, destroy_secret);
+
+ /* Now derive the key. */
+ if (crypto_pwhash(secret->data, sizeof(secret->data), passphrase, strlen(passphrase), salt,
+ /* INTERACTIVE needs 64 MiB of RAM, MODERATE needs 256,
+ * and SENSITIVE needs 1024. */
+ crypto_pwhash_argon2id_OPSLIMIT_MODERATE,
+ crypto_pwhash_argon2id_MEMLIMIT_MODERATE,
+ crypto_pwhash_ALG_ARGON2ID13) != 0) {
+ return tal_free(secret);
+ }
+
+ return secret;
+}
+
+bool hsm_secret_needs_passphrase(const u8 *hsm_secret, size_t len)
+{
+ switch (detect_hsm_secret_type(hsm_secret, len)) {
+ case HSM_SECRET_ENCRYPTED:
+ case HSM_SECRET_MNEMONIC_WITH_PASS:
+ return true;
+ case HSM_SECRET_PLAIN:
+ case HSM_SECRET_MNEMONIC_NO_PASS:
+ case HSM_SECRET_INVALID:
+ return false;
+ }
+ abort();
+}
+
+enum hsm_secret_type detect_hsm_secret_type(const u8 *hsm_secret, size_t len)
+{
+ /* Check for invalid cases first and return early */
+ if (len < HSM_SECRET_PLAIN_SIZE)
+ return HSM_SECRET_INVALID;
+
+ /* Legacy 32-byte plain format */
+ if (len == HSM_SECRET_PLAIN_SIZE)
+ return HSM_SECRET_PLAIN;
+
+ /* Legacy 73-byte encrypted format */
+ if (len == ENCRYPTED_HSM_SECRET_LEN)
+ return HSM_SECRET_ENCRYPTED;
+ assert(len > sizeof(struct sha256));
+ /* Check if it starts with our type bytes (mnemonic formats) */
+ if (memeqzero(hsm_secret, 32))
+ return HSM_SECRET_MNEMONIC_NO_PASS;
+ else
+ return HSM_SECRET_MNEMONIC_WITH_PASS;
+}
+
+/* Helper function to derive seed hash from mnemonic + passphrase */
+bool derive_seed_hash(const char *mnemonic, const char *passphrase, struct sha256 *seed_hash)
+{
+ if (!passphrase) {
+ /* No passphrase - return zero hash */
+ memset(seed_hash, 0, sizeof(*seed_hash));
+ return true;
+ }
+
+ u8 bip32_seed[BIP39_SEED_LEN_512];
+ size_t bip32_seed_len;
+
+ if (bip39_mnemonic_to_seed(mnemonic, passphrase, bip32_seed, sizeof(bip32_seed), &bip32_seed_len) != WALLY_OK)
+ return false;
+
+ sha256(seed_hash, bip32_seed, sizeof(bip32_seed));
+ return true;
+}
+
+static bool decrypt_hsm_secret(const struct secret *encryption_key,
+ const u8 *cipher,
+ struct secret *output)
+{
+ crypto_secretstream_xchacha20poly1305_state crypto_state;
+
+ /* The header part */
+ if (crypto_secretstream_xchacha20poly1305_init_pull(&crypto_state, cipher,
+ encryption_key->data) != 0)
+ return false;
+ /* The ciphertext part */
+ if (crypto_secretstream_xchacha20poly1305_pull(&crypto_state, output->data,
+ NULL, 0,
+ cipher + HS_HEADER_LEN,
+ HS_CIPHERTEXT_LEN,
+ NULL, 0) != 0)
+ return false;
+
+ return true;
+}
+
+/* Helper function to convert error codes to human-readable messages */
+const char *hsm_secret_error_str(enum hsm_secret_error err)
+{
+ switch (err) {
+ case HSM_SECRET_OK:
+ return "Success";
+ case HSM_SECRET_ERR_PASSPHRASE_REQUIRED:
+ return "Passphrase required but not provided";
+ case HSM_SECRET_ERR_PASSPHRASE_NOT_NEEDED:
+ return "Passphrase provided but not needed";
+ case HSM_SECRET_ERR_WRONG_PASSPHRASE:
+ return "Wrong passphrase";
+ case HSM_SECRET_ERR_INVALID_MNEMONIC:
+ return "Invalid mnemonic";
+ case HSM_SECRET_ERR_ENCRYPTION_FAILED:
+ return "Encryption failed";
+ case HSM_SECRET_ERR_SEED_DERIVATION_FAILED:
+ return "Could not derive seed from mnemonic";
+ case HSM_SECRET_ERR_INVALID_FORMAT:
+ return "Invalid hsm_secret format";
+ case HSM_SECRET_ERR_TERMINAL:
+ return "Terminal error";
+ case HSM_SECRET_ERR_MEMORY:
+ return "Memory error";
+ }
+ return "Unknown error";
+}
+
+static struct hsm_secret *extract_plain_secret(const tal_t *ctx,
+ const u8 *hsm_secret,
+ size_t len,
+ enum hsm_secret_error *err)
+{
+ struct hsm_secret *hsms = tal(ctx, struct hsm_secret);
+
+ assert(len == sizeof(hsms->secret));
+ hsms->type = HSM_SECRET_PLAIN;
+ hsms->mnemonic = NULL;
+ memcpy(&hsms->secret, hsm_secret, sizeof(hsms->secret));
+
+ *err = HSM_SECRET_OK;
+ return hsms;
+}
+
+static struct hsm_secret *extract_encrypted_secret(const tal_t *ctx,
+ const u8 *hsm_secret,
+ size_t len,
+ const char *passphrase,
+ enum hsm_secret_error *err)
+{
+ struct hsm_secret *hsms = tal(ctx, struct hsm_secret);
+ struct secret *encryption_key;
+ bool decrypt_success;
+
+ if (!passphrase) {
+ *err = HSM_SECRET_ERR_PASSPHRASE_REQUIRED;
+ return tal_free(hsms);
+ }
+ encryption_key = get_encryption_key(tmpctx, passphrase);
+ if (!encryption_key) {
+ *err = HSM_SECRET_ERR_WRONG_PASSPHRASE;
+ return tal_free(hsms);
+ }
+
+ /* Clear secret data first in case of partial decryption */
+ memset(&hsms->secret, 0, sizeof(hsms->secret));
+
+ /* Attempt decryption */
+ decrypt_success = decrypt_hsm_secret(encryption_key, hsm_secret, &hsms->secret);
+
+ /* Clear encryption key immediately after use */
+ destroy_secret(encryption_key);
+
+ if (!decrypt_success) {
+ /* Clear any partial decryption data */
+ memset(&hsms->secret, 0, sizeof(hsms->secret));
+ *err = HSM_SECRET_ERR_WRONG_PASSPHRASE;
+ return tal_free(hsms);
+ }
+
+ hsms->type = HSM_SECRET_ENCRYPTED;
+ hsms->mnemonic = NULL;
+
+ *err = HSM_SECRET_OK;
+ return hsms;
+}
+
+static struct hsm_secret *extract_mnemonic_secret(const tal_t *ctx,
+ const u8 *hsm_secret,
+ size_t len,
+ const char *passphrase,
+ enum hsm_secret_type type,
+ enum hsm_secret_error *err)
+{
+ struct hsm_secret *hsms = tal(ctx, struct hsm_secret);
+ const u8 *mnemonic_start;
+ size_t mnemonic_len;
+
+ assert(type == HSM_SECRET_MNEMONIC_NO_PASS || type == HSM_SECRET_MNEMONIC_WITH_PASS);
+ hsms->type = type;
+
+ /* Extract mnemonic portion (skip first 32 bytes which are passphrase hash) */
+ mnemonic_start = hsm_secret + PASSPHRASE_HASH_LEN;
+
+ assert(len > PASSPHRASE_HASH_LEN);
+ mnemonic_len = len - PASSPHRASE_HASH_LEN;
+
+ /* Copy into convenient string form */
+ hsms->mnemonic = tal_strndup(hsms, (const char *)mnemonic_start, mnemonic_len);
+
+ /* Validate passphrase if required */
+ if (type == HSM_SECRET_MNEMONIC_WITH_PASS) {
+ if (!passphrase) {
+ *err = HSM_SECRET_ERR_PASSPHRASE_REQUIRED;
+ return tal_free(hsms);
+ }
+
+ /* Validate passphrase by comparing stored hash with computed hash */
+ struct sha256 stored_hash, computed_hash;
+ memcpy(&stored_hash, hsm_secret, sizeof(stored_hash));
+ if (!derive_seed_hash(hsms->mnemonic, passphrase, &computed_hash)) {
+ *err = HSM_SECRET_ERR_SEED_DERIVATION_FAILED;
+ return tal_free(hsms);
+ }
+ if (!sha256_eq(&stored_hash, &computed_hash)) {
+ *err = HSM_SECRET_ERR_WRONG_PASSPHRASE;
+ return tal_free(hsms);
+ }
+ } else {
+ if (passphrase) {
+ *err = HSM_SECRET_ERR_PASSPHRASE_NOT_NEEDED;
+ return tal_free(hsms);
+ }
+ }
+
+ /* Validate mnemonic */
+ if (!validate_mnemonic(hsms->mnemonic, err)) {
+ return tal_free(hsms);
+ }
+
+ /* Derive the seed from the mnemonic */
+ u8 bip32_seed[BIP39_SEED_LEN_512];
+ size_t bip32_seed_len;
+
+ if (bip39_mnemonic_to_seed(hsms->mnemonic, passphrase, bip32_seed, sizeof(bip32_seed), &bip32_seed_len) != WALLY_OK) {
+ *err = HSM_SECRET_ERR_SEED_DERIVATION_FAILED;
+ return tal_free(hsms);
+ }
+
+ /* We only use the first 32 bytes for the hsm_secret */
+ memcpy(hsms->secret.data, bip32_seed, sizeof(hsms->secret.data));
+
+ *err = HSM_SECRET_OK;
+ return hsms;
+}
+
+/* If hsm_secret_needs_passphrase, passphrase must not be NULL.
+ * Returns NULL on failure. */
+struct hsm_secret *extract_hsm_secret(const tal_t *ctx,
+ const u8 *hsm_secret, size_t len,
+ const char *passphrase,
+ enum hsm_secret_error *err)
+{
+ enum hsm_secret_type type = detect_hsm_secret_type(hsm_secret, len);
+
+ switch (type) {
+ case HSM_SECRET_PLAIN:
+ return extract_plain_secret(ctx, hsm_secret, len, err);
+ case HSM_SECRET_ENCRYPTED:
+ return extract_encrypted_secret(ctx, hsm_secret, len, passphrase, err);
+ case HSM_SECRET_MNEMONIC_NO_PASS:
+ case HSM_SECRET_MNEMONIC_WITH_PASS:
+ return extract_mnemonic_secret(ctx, hsm_secret, len, passphrase, type, err);
+ case HSM_SECRET_INVALID:
+ *err = HSM_SECRET_ERR_INVALID_FORMAT;
+ return NULL;
+ }
+ abort();
+}
+
+bool encrypt_legacy_hsm_secret(const struct secret *encryption_key,
+ const struct secret *hsm_secret,
+ u8 *output)
+{
+ crypto_secretstream_xchacha20poly1305_state crypto_state;
+
+ if (crypto_secretstream_xchacha20poly1305_init_push(&crypto_state, output,
+ encryption_key->data) != 0)
+ return false;
+ if (crypto_secretstream_xchacha20poly1305_push(&crypto_state,
+ output + HS_HEADER_LEN,
+ NULL, hsm_secret->data,
+ sizeof(hsm_secret->data),
+ /* Additional data and tag */
+ NULL, 0, 0))
+ return false;
+
+ return true;
+}
+
+static void destroy_passphrase(char *passphrase)
+{
+ sodium_munlock(passphrase, tal_bytelen(passphrase));
+}
+
+/* Disable terminal echo if needed */
+static bool disable_echo(struct termios *saved_term)
+{
+ if (!isatty(fileno(stdin)))
+ return false;
+
+ if (tcgetattr(fileno(stdin), saved_term) != 0)
+ return false;
+
+ struct termios tmp = *saved_term;
+ tmp.c_lflag &= ~ECHO;
+
+ if (tcsetattr(fileno(stdin), TCSANOW, &tmp) != 0)
+ return false;
+
+ return true;
+}
+
+/* Restore terminal echo if it was disabled */
+static void restore_echo(const struct termios *saved_term)
+{
+ tcsetattr(fileno(stdin), TCSANOW, saved_term);
+}
+
+/* Read line from stdin (uses tal allocation) */
+static char *read_line(const tal_t *ctx)
+{
+ char *line = NULL;
+ size_t size = 0;
+
+ if (getline(&line, &size, stdin) < 0) {
+ free(line);
+ return NULL;
+ }
+
+ /* Strip newline */
+ size_t len = strlen(line);
+ if (len > 0 && line[len - 1] == '\n')
+ line[len - 1] = '\0';
+
+ /* Convert to tal string */
+ char *result = tal_strndup(ctx, line, len);
+ free(line);
+ return result;
+}
+
+const char *read_stdin_pass(const tal_t *ctx, enum hsm_secret_error *err)
+{
+ struct termios saved_term;
+ bool echo_disabled = disable_echo(&saved_term);
+ if (isatty(fileno(stdin)) && !echo_disabled) {
+ *err = HSM_SECRET_ERR_TERMINAL;
+ return NULL;
+ }
+
+ char *input = read_line(ctx);
+ if (!input) {
+ if (echo_disabled)
+ restore_echo(&saved_term);
+ *err = HSM_SECRET_ERR_INVALID_FORMAT;
+ return NULL;
+ }
+
+ /* Memory locking is mandatory: failure means we're on an insecure system */
+ if (sodium_mlock(input, tal_bytelen(input)) != 0)
+ abort();
+
+ tal_add_destructor(input, destroy_passphrase);
+
+ if (echo_disabled)
+ restore_echo(&saved_term);
+
+ *err = HSM_SECRET_OK;
+ return input;
+}
+
+const char *read_stdin_mnemonic(const tal_t *ctx, enum hsm_secret_error *err)
+{
+ printf("Introduce your BIP39 word list separated by space (at least 12 words):\n");
+ fflush(stdout);
+
+ char *line = read_line(ctx);
+ if (!line) {
+ *err = HSM_SECRET_ERR_INVALID_FORMAT;
+ return NULL;
+ }
+
+ /* Validate mnemonic */
+ if (!validate_mnemonic(line, err)) {
+ return NULL;
+ }
+
+ *err = HSM_SECRET_OK;
+ return line;
+}
+
+int is_legacy_hsm_secret_encrypted(const char *path)
+{
+ struct stat st;
+
+ if (stat(path, &st) != 0)
+ return -1;
+
+ return st.st_size == ENCRYPTED_HSM_SECRET_LEN;
+}
diff --git a/common/hsm_secret.h b/common/hsm_secret.h
new file mode 100644
index 0000000..0f32c23
--- /dev/null
+++ b/common/hsm_secret.h
@@ -0,0 +1,152 @@
+#ifndef LIGHTNING_COMMON_HSM_SECRET_H
+#define LIGHTNING_COMMON_HSM_SECRET_H
+#include "config.h"
+#include <bitcoin/privkey.h>
+#include <ccan/crypto/sha256/sha256.h>
+#include <ccan/tal/tal.h>
+#include <sodium.h>
+#include <sys/types.h>
+
+/* Length constants for encrypted HSM secret files */
+#define HS_HEADER_LEN crypto_secretstream_xchacha20poly1305_HEADERBYTES
+#define HS_CIPHERTEXT_LEN \
+ (sizeof(struct secret) + crypto_secretstream_xchacha20poly1305_ABYTES)
+#define ENCRYPTED_HSM_SECRET_LEN (HS_HEADER_LEN + HS_CIPHERTEXT_LEN)
+
+enum hsm_secret_type {
+ HSM_SECRET_PLAIN = 0, /* Legacy 32-byte format */
+ HSM_SECRET_ENCRYPTED = 1, /* Legacy 73-byte encrypted format */
+ HSM_SECRET_MNEMONIC_NO_PASS = 2, /* Mnemonic without passphrase */
+ HSM_SECRET_MNEMONIC_WITH_PASS = 3, /* Mnemonic with passphrase */
+ HSM_SECRET_INVALID = 4, /* When all else fails, blame the user */
+};
+
+enum hsm_secret_error {
+ HSM_SECRET_OK = 0,
+ HSM_SECRET_ERR_PASSPHRASE_REQUIRED,
+ HSM_SECRET_ERR_PASSPHRASE_NOT_NEEDED,
+ HSM_SECRET_ERR_WRONG_PASSPHRASE,
+ HSM_SECRET_ERR_INVALID_MNEMONIC,
+ HSM_SECRET_ERR_ENCRYPTION_FAILED,
+ HSM_SECRET_ERR_SEED_DERIVATION_FAILED,
+ HSM_SECRET_ERR_INVALID_FORMAT,
+ HSM_SECRET_ERR_TERMINAL,
+ HSM_SECRET_ERR_MEMORY
+};
+
+/**
+ * Represents the content of the hsm_secret file, either a raw seed or a mnemonic.
+ */
+struct hsm_secret {
+ enum hsm_secret_type type;
+ struct secret secret;
+ const char *mnemonic; /* NULL if not derived from mnemonic */
+};
+
+/**
+ * Checks whether the hsm_secret data requires a passphrase to decrypt.
+ * Handles legacy, encrypted, and mnemonic-based formats.
+ */
+bool hsm_secret_needs_passphrase(const u8 *hsm_secret, size_t len);
+
+/**
+ * Parse and decrypt an hsm_secret file.
+ *
+ * @ctx - a tal context
+ * @hsm_secret - raw file contents
+ * @len - length of file
+ * @passphrase - passphrase, or NULL if not needed
+ * @err - optional pointer to set error code on failure
+ *
+ * Returns parsed `struct hsm_secret` or NULL on error.
+ */
+struct hsm_secret *extract_hsm_secret(const tal_t *ctx,
+ const u8 *hsm_secret, size_t len,
+ const char *passphrase,
+ enum hsm_secret_error *err);
+
+
+/**
+ * get_encryption_key - Derive encryption key from passphrase using Argon2.
+ * @ctx - tal context for allocation
+ * @passphrase - the passphrase to derive from
+ *
+ * Returns derived encryption key, or NULL on error.
+ * The returned key is memory-locked and has a destructor to clear it.
+ */
+struct secret *get_encryption_key(const tal_t *ctx, const char *passphrase);
+
+/**
+ * Encrypt a given hsm_secret using a provided encryption key.
+ * @encryption_key - derived from passphrase (via Argon2)
+ * @hsm_secret - plaintext secret to encrypt
+ * @output - output buffer for encrypted data (must be ENCRYPTED_HSM_SECRET_LEN bytes)
+ *
+ * Returns true on success.
+ */
+bool encrypt_legacy_hsm_secret(const struct secret *encryption_key,
+ const struct secret *hsm_secret,
+ u8 *output);
+
+/**
+ * Reads a passphrase from stdin, disabling terminal echo.
+ * Returns a newly allocated string on success, NULL on error.
+ * @ctx - tal context for allocation
+ * @err - on failure, this will be set to the error code
+ *
+ * Returns allocated passphrase or NULL on error.
+ */
+const char *read_stdin_pass(const tal_t *ctx, enum hsm_secret_error *err);
+
+/**
+ * Convert error code to human-readable string.
+ * @err - the error code to convert
+ *
+ * Returns a string describing the error.
+ */
+const char *hsm_secret_error_str(enum hsm_secret_error err);
+
+/**
+ * Detect the type of hsm_secret based on its content and length.
+ * @hsm_secret - raw file contents
+ * @len - length of file
+ *
+ * Returns the detected type.
+ */
+enum hsm_secret_type detect_hsm_secret_type(const u8 *hsm_secret, size_t len);
+
+/**
+ * Reads a BIP39 mnemonic from stdin with validation.
+ * Returns a newly allocated string on success, NULL on error.
+ * @ctx - tal context for allocation
+ * @err - optional pointer to set error code on failure
+ *
+ * Returns tal-allocated mnemonic string or NULL on error.
+ */
+const char *read_stdin_mnemonic(const tal_t *ctx, enum hsm_secret_error *err);
+
+/**
+ * Derive seed hash from mnemonic + passphrase.
+ * @mnemonic - the BIP39 mnemonic
+ * @passphrase - the passphrase (can be NULL)
+ * @seed_hash - output parameter for the derived seed hash
+ *
+ * Returns true on success, false on failure.
+ */
+bool derive_seed_hash(const char *mnemonic, const char *passphrase, struct sha256 *seed_hash);
+
+/**
+ * Check if hsm_secret file is encrypted (legacy format only).
+ * @path - path to the hsm_secret file
+ *
+ * Returns 1 if encrypted, 0 if not encrypted, -1 on error.
+ */
+int is_legacy_hsm_secret_encrypted(const char *path);
+
+/**
+ * Zero and unlock a secret's memory.
+ * @secret - the secret to destroy
+ */
+void destroy_secret(struct secret *secret);
+
+#endif /* LIGHTNING_COMMON_HSM_SECRET_H */
Why this scored 11/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.