Skip to content

Commit 2e887e1

Browse files
committed
Add in function to read a mnemonic from stdin
1 parent 85fc8c6 commit 2e887e1

4 files changed

Lines changed: 90 additions & 61 deletions

File tree

common/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ COMMON_SRC_NOGEN := \
4646
common/hmac.c \
4747
common/hsm_capable.c \
4848
common/hsm_encryption.c \
49+
common/hsm_secret.c \
4950
common/htlc_state.c \
5051
common/htlc_trim.c \
5152
common/htlc_tx.c \

common/hsm_secret.c

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,48 @@ const char *read_stdin_pass(const tal_t *ctx, enum hsm_secret_error *err)
440440
return passphrase;
441441
}
442442

443+
/* Add this function to hsm_secret.c */
444+
const char *read_stdin_mnemonic(const tal_t *ctx, enum hsm_secret_error *err)
445+
{
446+
*err = HSM_SECRET_OK;
447+
448+
printf("Introduce your BIP39 word list separated by space (at least 12 words):\n");
449+
fflush(stdout);
450+
451+
char *line = NULL;
452+
size_t size = 0;
453+
if (getline(&line, &size, stdin) < 0) {
454+
free(line);
455+
*err = HSM_SECRET_ERR_INVALID_FORMAT;
456+
return NULL;
457+
}
458+
459+
/* Strip newline */
460+
size_t len = strlen(line);
461+
if (len > 0 && line[len - 1] == '\n')
462+
line[len - 1] = '\0';
463+
464+
/* Validate mnemonic */
465+
struct words *words;
466+
if (bip39_get_wordlist("en", &words) != WALLY_OK) {
467+
free(line);
468+
*err = HSM_SECRET_ERR_WORDLIST_FAILED;
469+
return NULL;
470+
}
471+
472+
if (bip39_mnemonic_validate(words, line) != WALLY_OK) {
473+
free(line);
474+
*err = HSM_SECRET_ERR_INVALID_MNEMONIC;
475+
return NULL;
476+
}
477+
478+
/* Convert to tal string */
479+
char *mnemonic = tal_strdup(ctx, line);
480+
free(line);
481+
482+
return mnemonic;
483+
}
484+
443485

444486

445487

common/hsm_secret.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,14 @@ enum hsm_secret_type detect_hsm_secret_type(const u8 *hsm_secret, size_t len);
143143
*/
144144
bool validate_mnemonic_passphrase(const u8 *hsm_secret, size_t len, const char *passphrase);
145145

146+
/**
147+
* Reads a BIP39 mnemonic from stdin with validation.
148+
* Returns a newly allocated string on success, NULL on error.
149+
* @ctx - tal context for allocation
150+
* @err - optional pointer to set error code on failure
151+
*
152+
* Returns tal-allocated mnemonic string or NULL on error.
153+
*/
154+
const char *read_stdin_mnemonic(const tal_t *ctx, enum hsm_secret_error *err);
155+
146156
#endif /* LIGHTNING_COMMON_HSM_SECRET_H */

tools/hsmtool.c

Lines changed: 37 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -451,64 +451,25 @@ static int guess_to_remote(const char *address, struct node_id *node_id,
451451
return 1;
452452
}
453453

454-
static char *get_mnemonic(void) {
455-
char *line = NULL;
456-
size_t line_size = 0;
457-
458-
printf("Introduce your BIP39 word list separated by space (at least 12 words):\n");
459-
fflush(stdout);
460-
size_t characters = getline(&line, &line_size, stdin);
461-
if (characters < 0)
462-
errx(ERROR_USAGE, "Could not read line from stdin.");
463-
line[characters-1] = '\0';
464-
return line;
465-
}
466-
467-
static char *read_mnemonic(void) {
468-
char *mnemonic;
469-
struct words *words;
470-
471-
/* Get English wordlist */
472-
if (bip39_get_wordlist("en", &words) != WALLY_OK)
473-
errx(ERROR_LIBWALLY, "Could not load English wordlist");
474-
475-
/* Get mnemonic from user */
476-
mnemonic = get_mnemonic();
477-
478-
/* Validate mnemonic */
479-
if (bip39_mnemonic_validate(words, mnemonic) != WALLY_OK) {
480-
errx(ERROR_USAGE, "Invalid mnemonic: \"%s\"", mnemonic);
481-
}
482-
return mnemonic;
483-
}
484-
485454
static int generate_hsm(const char *hsm_secret_path)
486455
{
487-
char *mnemonic;
488-
const char *passphrase;
489-
struct words *words;
490-
enum hsm_secret_error pass_err;
491-
492-
/* Get English wordlist */
493-
if (bip39_get_wordlist("en", &words) != WALLY_OK)
494-
errx(ERROR_LIBWALLY, "Could not load English wordlist");
495-
496-
/* Get mnemonic from user */
497-
mnemonic = get_mnemonic();
456+
const char *mnemonic, *passphrase;
457+
enum hsm_secret_error err;
498458

499-
/* Validate mnemonic */
500-
if (bip39_mnemonic_validate(words, mnemonic) != WALLY_OK)
501-
errx(ERROR_USAGE, "Invalid mnemonic: \"%s\"", mnemonic);
459+
/* Get mnemonic from user using consistent interface */
460+
mnemonic = read_stdin_mnemonic(tmpctx, &err);
461+
if (!mnemonic)
462+
errx(EXITCODE_ERROR_HSM_FILE, "Could not read mnemonic: %s", hsm_secret_error_str(err));
502463

503464
/* Get optional passphrase */
504465
printf("Warning: remember that different passphrases yield different "
505466
"bitcoin wallets.\n");
506467
printf("If left empty, no password is used (echo is disabled).\n");
507468
printf("Enter your passphrase: \n");
508469
fflush(stdout);
509-
passphrase = read_stdin_pass(tmpctx, &pass_err);
470+
passphrase = read_stdin_pass(tmpctx, &err);
510471
if (!passphrase)
511-
errx(EXITCODE_ERROR_HSM_FILE, "Could not read passphrase: %s", hsm_secret_error_str(pass_err));
472+
errx(EXITCODE_ERROR_HSM_FILE, "Could not read passphrase: %s", hsm_secret_error_str(err));
512473
if (strlen(passphrase) == 0) {
513474
passphrase = NULL;
514475
}
@@ -549,8 +510,7 @@ static int generate_hsm(const char *hsm_secret_path)
549510
printf("Remember your passphrase - it's required to use this hsm_secret!\n");
550511
}
551512

552-
free(mnemonic);
553-
/* passphrase will be automatically cleaned up by tmpctx */
513+
/* passphrase and mnemonic will be automatically cleaned up by tmpctx */
554514
return 0;
555515
}
556516

@@ -617,39 +577,55 @@ static int dumponchaindescriptors(const char *hsm_secret_path,
617577

618578
static int check_hsm(const char *hsm_secret_path)
619579
{
620-
char *mnemonic;
621-
struct secret hsm_secret;
580+
struct secret file_secret, derived_secret;
622581
u8 bip32_seed[BIP39_SEED_LEN_512];
623582
size_t bip32_seed_len;
624-
const char *passphrase;
625-
enum hsm_secret_error pass_err;
583+
const char *passphrase, *mnemonic;
584+
enum hsm_secret_error err;
626585

627-
get_hsm_seed(&hsm_secret, hsm_secret_path);
586+
/* Check what type of hsm_secret we're dealing with */
587+
u8 *contents = grab_file(tmpctx, hsm_secret_path);
588+
if (!contents)
589+
errx(EXITCODE_ERROR_HSM_FILE, "Reading hsm_secret");
590+
tal_resize(&contents, tal_bytelen(contents) - 1);
591+
592+
enum hsm_secret_type type = detect_hsm_secret_type(contents, tal_bytelen(contents));
628593

594+
/* Get the actual seed from the file */
595+
get_hsm_seed(&file_secret, hsm_secret_path);
596+
597+
/* Ask user for their BIP39 backup passphrase */
629598
printf("Warning: remember that different passphrases yield different "
630599
"bitcoin wallets.\n");
631600
printf("If left empty, no password is used (echo is disabled).\n");
632601
printf("Enter your passphrase: \n");
633602
fflush(stdout);
634-
passphrase = read_stdin_pass(tmpctx, &pass_err);
603+
passphrase = read_stdin_pass(tmpctx, &err);
635604
if (!passphrase)
636-
errx(EXITCODE_ERROR_HSM_FILE, "Could not read passphrase: %s", hsm_secret_error_str(pass_err));
605+
errx(EXITCODE_ERROR_HSM_FILE, "Could not read passphrase: %s", hsm_secret_error_str(err));
637606
if (strlen(passphrase) == 0) {
638607
passphrase = NULL;
639608
}
640609

641-
mnemonic = read_mnemonic();
610+
/* Ask user for their backup mnemonic using consistent interface */
611+
mnemonic = read_stdin_mnemonic(tmpctx, &err);
612+
if (!mnemonic)
613+
errx(EXITCODE_ERROR_HSM_FILE, "Could not read mnemonic: %s", hsm_secret_error_str(err));
614+
615+
/* Derive seed from user's backup mnemonic + passphrase */
642616
if (bip39_mnemonic_to_seed(mnemonic, passphrase, bip32_seed, sizeof(bip32_seed), &bip32_seed_len) != WALLY_OK)
643617
errx(ERROR_LIBWALLY, "Unable to derive BIP32 seed from BIP39 mnemonic");
644618

645-
/* We only use first 32 bytes */
646-
if (memcmp(bip32_seed, hsm_secret.data, sizeof(hsm_secret.data)) != 0)
619+
/* Copy first 32 bytes to our secret for comparison */
620+
memcpy(derived_secret.data, bip32_seed, sizeof(derived_secret.data));
621+
622+
/* Compare the seeds - this works for all formats */
623+
if (memcmp(derived_secret.data, file_secret.data, sizeof(file_secret.data)) != 0)
647624
errx(ERROR_KEYDERIV, "resulting hsm_secret did not match");
648625

649626
printf("OK\n");
650627

651-
free(mnemonic);
652-
/* passphrase will be automatically cleaned up by tmpctx */
628+
/* passphrase and mnemonic will be automatically cleaned up by tmpctx */
653629
return 0;
654630
}
655631

0 commit comments

Comments
 (0)