2025-10-24 13:57:41 +10:30
|
|
|
#include "config.h"
|
|
|
|
|
#include <assert.h>
|
|
|
|
|
#include <ccan/mem/mem.h>
|
2025-10-24 13:57:43 +10:30
|
|
|
#include <ccan/tal/grab_file/grab_file.h>
|
2025-10-24 13:57:41 +10:30
|
|
|
#include <ccan/tal/str/str.h>
|
|
|
|
|
#include <common/errcode.h>
|
|
|
|
|
#include <common/hsm_secret.h>
|
2025-10-24 13:57:44 +10:30
|
|
|
#include <common/memleak.h>
|
2025-10-24 13:57:41 +10:30
|
|
|
#include <common/utils.h>
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
#include <termios.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <wally_bip39.h>
|
|
|
|
|
|
2025-10-24 13:57:45 +10:30
|
|
|
/* HSM secret size constants */
|
|
|
|
|
#define HSM_SECRET_PLAIN_SIZE 32
|
|
|
|
|
#define HSM_SECRET_MNEMONIC_SIZE 64
|
|
|
|
|
|
2025-10-24 13:57:41 +10:30
|
|
|
/* 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
|
|
|
|
|
|
|
|
|
|
/* Helper function to validate a mnemonic string */
|
2026-01-13 13:19:18 +10:30
|
|
|
enum hsm_secret_error validate_mnemonic(const char *mnemonic)
|
2025-10-24 13:57:41 +10:30
|
|
|
{
|
|
|
|
|
struct words *words;
|
2025-10-24 13:57:44 +10:30
|
|
|
bool ok;
|
2025-10-24 13:57:41 +10:30
|
|
|
|
2025-10-24 13:57:44 +10:30
|
|
|
tal_wally_start();
|
2025-10-24 13:57:41 +10:30
|
|
|
if (bip39_get_wordlist("en", &words) != WALLY_OK) {
|
|
|
|
|
abort();
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-24 13:57:44 +10:30
|
|
|
ok = (bip39_mnemonic_validate(words, mnemonic) == WALLY_OK);
|
|
|
|
|
|
|
|
|
|
/* Wordlists can persist, so provide a common context! */
|
|
|
|
|
tal_wally_end(notleak_with_children(tal(NULL, char)));
|
|
|
|
|
|
2026-01-13 13:19:18 +10:30
|
|
|
if (!ok)
|
|
|
|
|
return HSM_SECRET_ERR_INVALID_MNEMONIC;
|
2025-10-24 13:57:41 +10:30
|
|
|
|
2026-01-13 13:19:18 +10:30
|
|
|
return HSM_SECRET_OK;
|
2025-10-24 13:57:41 +10:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 ! */
|
2025-10-24 13:57:50 +10:30
|
|
|
mlock_tal_memory(secret);
|
2025-10-24 13:57:41 +10:30
|
|
|
|
|
|
|
|
/* 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;
|
2025-10-24 13:57:52 +10:30
|
|
|
|
|
|
|
|
/* Since HSM_SECRET_PLAIN_SIZE == 32, this must be true! */
|
|
|
|
|
assert(len >= sizeof(struct sha256));
|
|
|
|
|
|
|
|
|
|
/* First 32 bytes are the hash of the resulting seed: all 0
|
|
|
|
|
* for "no passphrase" */
|
|
|
|
|
if (memeqzero(hsm_secret, sizeof(struct sha256)))
|
2025-10-24 13:57:41 +10:30
|
|
|
return HSM_SECRET_MNEMONIC_NO_PASS;
|
|
|
|
|
else
|
|
|
|
|
return HSM_SECRET_MNEMONIC_WITH_PASS;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-24 13:57:44 +10:30
|
|
|
struct bip32_seed {
|
|
|
|
|
u8 seed[BIP39_SEED_LEN_512];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static bool mnemonic_to_seed(const char *mnemonic, const char *passphrase,
|
|
|
|
|
struct bip32_seed *bip32_seed)
|
|
|
|
|
{
|
|
|
|
|
size_t len;
|
|
|
|
|
|
|
|
|
|
tal_wally_start();
|
|
|
|
|
if (bip39_mnemonic_to_seed(mnemonic, passphrase,
|
|
|
|
|
bip32_seed->seed, sizeof(bip32_seed->seed),
|
|
|
|
|
&len) != WALLY_OK) {
|
|
|
|
|
tal_wally_end(tmpctx);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
/* libwally only allocate the salt temporarily, so context
|
|
|
|
|
* doesn't matter. */
|
|
|
|
|
tal_wally_end(tmpctx);
|
|
|
|
|
|
|
|
|
|
assert(len == sizeof(bip32_seed->seed));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-24 13:57:41 +10:30
|
|
|
/* Helper function to derive seed hash from mnemonic + passphrase */
|
|
|
|
|
bool derive_seed_hash(const char *mnemonic, const char *passphrase, struct sha256 *seed_hash)
|
|
|
|
|
{
|
2025-10-24 13:57:44 +10:30
|
|
|
struct bip32_seed bip32_seed;
|
|
|
|
|
|
2025-10-24 13:57:41 +10:30
|
|
|
if (!passphrase) {
|
|
|
|
|
/* No passphrase - return zero hash */
|
|
|
|
|
memset(seed_hash, 0, sizeof(*seed_hash));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-24 13:57:44 +10:30
|
|
|
if (!mnemonic_to_seed(mnemonic, passphrase, &bip32_seed))
|
2025-10-24 13:57:41 +10:30
|
|
|
return false;
|
|
|
|
|
|
2025-10-24 13:57:44 +10:30
|
|
|
sha256(seed_hash, bip32_seed.seed, sizeof(bip32_seed.seed));
|
2025-10-24 13:57:41 +10:30
|
|
|
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";
|
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
|
|
2025-10-24 13:57:47 +10:30
|
|
|
assert(len == HSM_SECRET_PLAIN_SIZE);
|
2025-10-24 13:57:41 +10:30
|
|
|
hsms->type = HSM_SECRET_PLAIN;
|
|
|
|
|
hsms->mnemonic = NULL;
|
2025-10-24 13:57:45 +10:30
|
|
|
|
|
|
|
|
/* Allocate and populate secret_data (new field) */
|
|
|
|
|
hsms->secret_data = tal_dup_arr(hsms, u8, hsm_secret, HSM_SECRET_PLAIN_SIZE, 0);
|
|
|
|
|
|
2025-10-24 13:57:41 +10:30
|
|
|
*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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Attempt decryption */
|
2025-10-24 13:57:47 +10:30
|
|
|
struct secret temp_secret;
|
|
|
|
|
decrypt_success = decrypt_hsm_secret(encryption_key, hsm_secret, &temp_secret);
|
2025-10-24 13:57:41 +10:30
|
|
|
if (!decrypt_success) {
|
|
|
|
|
*err = HSM_SECRET_ERR_WRONG_PASSPHRASE;
|
|
|
|
|
return tal_free(hsms);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-24 13:57:47 +10:30
|
|
|
/* Duplicate decrypted secret data */
|
|
|
|
|
hsms->secret_data = tal_dup_arr(hsms, u8, temp_secret.data, HSM_SECRET_PLAIN_SIZE, 0);
|
2025-10-24 13:57:45 +10:30
|
|
|
|
2025-10-24 13:57:41 +10:30
|
|
|
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;
|
2025-10-24 13:57:44 +10:30
|
|
|
struct bip32_seed bip32_seed;
|
2025-10-24 13:57:41 +10:30
|
|
|
|
|
|
|
|
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 */
|
2026-01-13 13:19:18 +10:30
|
|
|
*err = validate_mnemonic(hsms->mnemonic);
|
|
|
|
|
if (*err != HSM_SECRET_OK) {
|
2025-10-24 13:57:41 +10:30
|
|
|
return tal_free(hsms);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-24 13:57:44 +10:30
|
|
|
if (!mnemonic_to_seed(hsms->mnemonic, passphrase, &bip32_seed)) {
|
2025-10-24 13:57:41 +10:30
|
|
|
*err = HSM_SECRET_ERR_SEED_DERIVATION_FAILED;
|
|
|
|
|
return tal_free(hsms);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-24 13:57:45 +10:30
|
|
|
/* 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);
|
|
|
|
|
|
2025-10-24 13:57:41 +10:30
|
|
|
*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);
|
|
|
|
|
|
2025-10-24 13:57:44 +10:30
|
|
|
/* Switch will cause gcc to complain if a type isn't handled. */
|
2025-10-24 13:57:41 +10:30
|
|
|
switch (type) {
|
|
|
|
|
case HSM_SECRET_PLAIN:
|
|
|
|
|
return extract_plain_secret(ctx, hsm_secret, len, err);
|
2025-10-24 13:57:44 +10:30
|
|
|
|
2025-10-24 13:57:41 +10:30
|
|
|
case HSM_SECRET_ENCRYPTED:
|
|
|
|
|
return extract_encrypted_secret(ctx, hsm_secret, len, passphrase, err);
|
2025-10-24 13:57:44 +10:30
|
|
|
|
2025-10-24 13:57:41 +10:30
|
|
|
case HSM_SECRET_MNEMONIC_NO_PASS:
|
|
|
|
|
case HSM_SECRET_MNEMONIC_WITH_PASS:
|
|
|
|
|
return extract_mnemonic_secret(ctx, hsm_secret, len, passphrase, type, err);
|
2025-10-24 13:57:44 +10:30
|
|
|
|
2025-10-24 13:57:41 +10:30
|
|
|
case HSM_SECRET_INVALID:
|
|
|
|
|
*err = HSM_SECRET_ERR_INVALID_FORMAT;
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
2025-10-24 13:57:44 +10:30
|
|
|
|
|
|
|
|
/* detect_hsm_secret_type promised to return a valid type. */
|
2025-10-24 13:57:41 +10:30
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 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) */
|
2025-10-24 13:57:52 +10:30
|
|
|
static const char *read_line(const tal_t *ctx)
|
2025-10-24 13:57:41 +10:30
|
|
|
{
|
|
|
|
|
char *line = NULL;
|
|
|
|
|
size_t size = 0;
|
|
|
|
|
|
|
|
|
|
if (getline(&line, &size, stdin) < 0) {
|
|
|
|
|
free(line);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Strip newline */
|
|
|
|
|
size_t len = strlen(line);
|
2025-10-24 13:57:51 +10:30
|
|
|
if (strends(line, "\n"))
|
|
|
|
|
len--;
|
2025-10-24 13:57:41 +10:30
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-24 13:57:52 +10:30
|
|
|
const char *input = read_line(ctx);
|
2025-10-24 13:57:41 +10:30
|
|
|
if (!input) {
|
|
|
|
|
if (echo_disabled)
|
|
|
|
|
restore_echo(&saved_term);
|
|
|
|
|
*err = HSM_SECRET_ERR_INVALID_FORMAT;
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-24 13:57:50 +10:30
|
|
|
mlock_tal_memory(input);
|
2025-10-24 13:57:41 +10:30
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2025-10-24 13:57:52 +10:30
|
|
|
const char *line = read_line(ctx);
|
2025-10-24 13:57:41 +10:30
|
|
|
if (!line) {
|
|
|
|
|
*err = HSM_SECRET_ERR_INVALID_FORMAT;
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Validate mnemonic */
|
2026-01-13 13:19:18 +10:30
|
|
|
*err = validate_mnemonic(line);
|
|
|
|
|
if (*err != HSM_SECRET_OK) {
|
|
|
|
|
return tal_free(line);
|
2025-10-24 13:57:41 +10:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*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;
|
|
|
|
|
}
|
2025-10-24 13:57:43 +10:30
|
|
|
|
|
|
|
|
const char *format_type_name(enum hsm_secret_type type)
|
|
|
|
|
{
|
|
|
|
|
switch (type) {
|
|
|
|
|
case HSM_SECRET_PLAIN:
|
|
|
|
|
return "plain (32-byte binary)";
|
|
|
|
|
case HSM_SECRET_ENCRYPTED:
|
|
|
|
|
return "encrypted (73-byte binary)";
|
|
|
|
|
case HSM_SECRET_MNEMONIC_NO_PASS:
|
|
|
|
|
return "mnemonic (no password)";
|
|
|
|
|
case HSM_SECRET_MNEMONIC_WITH_PASS:
|
|
|
|
|
return "mnemonic (with password)";
|
|
|
|
|
case HSM_SECRET_INVALID:
|
|
|
|
|
return "invalid";
|
|
|
|
|
}
|
|
|
|
|
return "unknown";
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-24 13:57:46 +10:30
|
|
|
bool is_mnemonic_secret(size_t secret_len)
|
|
|
|
|
{
|
|
|
|
|
return secret_len == HSM_SECRET_MNEMONIC_SIZE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool use_bip86_derivation(size_t secret_len)
|
|
|
|
|
{
|
|
|
|
|
/* BIP86 was introduced alongside mnemonic support, so they're available together */
|
|
|
|
|
return is_mnemonic_secret(secret_len);
|
|
|
|
|
}
|