Browse Source

`hsm_secret` generation from a seed-phrase

tools: Add `generatehsm` method to hsmtool to derivate BIP32 seeds from a
mnemonic using the BIP39 standard.

The new method uses libwally for the BIP39 to BIP32 derivation. It also
fails if an hsm_secret file already exists, so we do not overwrite
someone else's wallet without noticing.

It allows the use of passphrases, the ECHO mode in the terminal is
disable for higher security.

It currently supports "en", "es", "fr", "it", "jp", "zhs", "zht".

Changelog-Added: hsmtool: `hsm_secret` generation from a seed-phrase following BIP39.
ppa-prep
positiveblue 4 years ago
committed by neil saitug
parent
commit
fa1483a00d
  1. 6
      doc/lightning-hsmtool.8
  2. 3
      doc/lightning-hsmtool.8.md
  3. 160
      tools/hsmtool.c

6
doc/lightning-hsmtool.8

@ -51,6 +51,10 @@ and is usually no greater than the number of channels that the node has
ever had\.
Specify \fIpassword\fR if the \fBhsm_secret\fR is encrypted\.
\fBgeneratehsm\fR \fIhsm_secret_path\fR
Generates a new hsm_secret using BIP39\.
.SH BUGS
You should report bugs on our github issues page, and maybe submit a fix
@ -76,4 +80,4 @@ Note: the modules in the ccan/ directory have their own licenses, but
the rest of the code is covered by the BSD-style MIT license\.
Main web site: \fIhttps://github.com/ElementsProject/lightning\fR
\" SHA256STAMP:11b3e6c050eb31e7faac10e8b75c3f7b7d57269f7f8c47c67f6d115b7a479c09
\" SHA256STAMP:918981692d3840344e15c539b007b473d5ea0ad481145eccff092bf61ec6ddb0

3
doc/lightning-hsmtool.8.md

@ -48,6 +48,9 @@ and is usually no greater than the number of channels that the node has
ever had.
Specify *password* if the `hsm_secret` is encrypted.
**generatehsm** *hsm\_secret\_path*
Generates a new hsm_secret using BIP39.
BUGS
----

160
tools/hsmtool.c

@ -1,4 +1,5 @@
#include <bitcoin/privkey.h>
#include <ccan/array_size/array_size.h>
#include <ccan/crypto/hkdf_sha256/hkdf_sha256.h>
#include <ccan/err/err.h>
#include <ccan/noerr/noerr.h>
@ -13,15 +14,19 @@
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <sodium.h>
#include <sys/stat.h>
#include <termios.h>
#include <unistd.h>
#include <wally_bip39.h>
#define ERROR_HSM_FILE errno
#define ERROR_USAGE 2
#define ERROR_LIBSODIUM 3
#define ERROR_LIBWALLY 4
#define ERROR_KEYDERIV 5
#define ERROR_LANG_NOT_SUPPORTED 6
static void show_usage(const char *progname)
{
@ -33,6 +38,7 @@ static void show_usage(const char *progname)
"<path/to/hsm_secret> [hsm_secret password]\n");
printf(" - guesstoremote <P2WPKH address> <node id> <tries> "
"<path/to/hsm_secret> [hsm_secret password]\n");
printf(" - generatehsm <path/to/new//hsm_secret>\n");
exit(0);
}
@ -368,6 +374,145 @@ static int guess_to_remote(const char *address, struct node_id *node_id,
return 1;
}
static void get_words(struct words **words) {
struct wordlist_lang {
char *abbr;
char *name;
};
struct wordlist_lang languages[] = {
{"en", "English"},
{"es", "Spanish"},
{"fr", "French"},
{"it", "Italian"},
{"jp", "Japanese"},
{"zhs", "Chinese Simplified"},
{"zht", "Chinese Traditional"},
};
printf("Select your language:\n");
for (size_t i = 0; i < ARRAY_SIZE(languages); i++) {
printf(" %zu) %s (%s)\n", i, languages[i].name, languages[i].abbr);
}
printf("Select [0-%zu]: ", ARRAY_SIZE(languages));
char *selected = NULL;
size_t size = 0;
size_t characters = getline(&selected, &size, stdin);
if (characters < 0)
errx(ERROR_USAGE, "Could not read line from stdin.");
/* To distinguish success/failure after call */
errno = 0;
char *endptr;
long val = strtol(selected, &endptr, 10);
if (errno == ERANGE || (errno != 0 && val == 0) || endptr == selected || val < 0 || val >= ARRAY_SIZE(languages))
errx(ERROR_USAGE, "Invalid language selection, select one from the list [0-6].");
bip39_get_wordlist(languages[val].abbr, words);
}
static void get_mnemonic(char *mnemonic) {
char *line = NULL;
size_t line_size = 0;
printf("Introduce your BIP39 word list separated by space:\n");
size_t characters = getline(&line, &line_size, stdin);
if (characters < 0)
errx(ERROR_USAGE, "Could not read line from stdin.");
line[characters-1] = '\0';
strcpy(mnemonic, line);
free(line);
}
static void read_mnemonic(char *mnemonic) {
/* Get words for the mnemonic language */
struct words *words;
get_words(&words);
/* Get mnemonic */
get_mnemonic(mnemonic);
if (bip39_mnemonic_validate(words, mnemonic) != 0) {
errx(ERROR_USAGE, "Invalid mnemonic: \"%s\"", mnemonic);
}
}
static void read_passphrase(char **passphrase) {
struct termios current_term, temp_term;
printf("Warning: remember that different passphrases yield different "
"bitcoin wallets.\n");
printf("If left empty, no password is used (echo is "
"disabled now).\n");
printf("Enter your passphrase: \n");
/* Change terminal options so we do not echo the passphrase */
if (tcgetattr(fileno(stdin), &current_term) != 0)
errx(ERROR_USAGE, "Could not get current terminal options.");
temp_term = current_term;
temp_term.c_lflag &= ~ECHO;
if (tcsetattr(fileno(stdin), TCSAFLUSH, &temp_term) != 0)
errx(ERROR_USAGE, "Could not disable passphrase echoing.");
/* If we don't flush we might end up being buffered and we might seem
* to hang while we wait for the password. */
fflush(stdout);
size_t passphrase_size = 0;
size_t characters = getline(passphrase, &passphrase_size, stdin);
if (characters < 0)
errx(ERROR_USAGE, "Could not read passphrase from stdin.");
/* Newline is not part of the valid passphrase */
if ( (*passphrase)[characters-1] == '\n' ) {
(*passphrase)[characters-1] = '\0';
}
/* If the user did not introduce any password, we want to set passphrase
* to NULL not to '\0' for libwally */
if (strlen(*passphrase) == 0) {
free(*passphrase);
*passphrase = NULL;
}
if (tcsetattr(fileno(stdin), TCSAFLUSH, &current_term) != 0)
errx(ERROR_USAGE, "Could not restore terminal options.");
}
static int generate_hsm(const char *hsm_secret_path)
{
char mnemonic[BIP39_WORDLIST_LEN];
read_mnemonic(mnemonic);
char *passphrase = NULL;
read_passphrase(&passphrase);
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)
errx(ERROR_LIBWALLY, "Unable to derive BIP32 seed from BIP39 mnemonic");
int fd = open(hsm_secret_path, O_CREAT|O_EXCL|O_WRONLY, 0400);
if (fd < 0) {
errx(ERROR_USAGE, "Unable to create hsm_secret file");
}
if (!write_all(fd, bip32_seed, bip32_seed_len))
errx(ERROR_USAGE, "Error writing secret to hsm_secret file");
if (fsync(fd) != 0)
errx(ERROR_USAGE, "Error fsyncing hsm_secret file");
/* This should never fail if fsync succeeded. But paranoia is good, and bugs exist */
if (close(fd) != 0)
errx(ERROR_USAGE, "Error closing hsm_secret file");
printf("New hsm_secret file created at %s\n", hsm_secret_path);
printf("Use the `encrypt` command to encrypt the BIP32 seed if needed\n");
free(passphrase);
return 0;
}
int main(int argc, char *argv[])
{
const char *method;
@ -413,5 +558,20 @@ int main(int argc, char *argv[])
argv[5], argc >= 7 ? argv[6] : NULL);
}
if (streq(method, "generatehsm")) {
if (argc != 3)
show_usage(argv[0]);
char *hsm_secret_path = argv[2];
/* if hsm_secret already exists we abort the process
* we do not want to lose someone else's funds */
struct stat st;
if (stat(hsm_secret_path, &st) == 0)
errx(ERROR_USAGE, "hsm_secret file at %s already exists", hsm_secret_path);
return generate_hsm(hsm_secret_path);
}
show_usage(argv[0]);
}

Loading…
Cancel
Save