diff --git a/doc/lightning-hsmtool.8 b/doc/lightning-hsmtool.8 index f40d174fe..8057c86b5 100644 --- a/doc/lightning-hsmtool.8 +++ b/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 diff --git a/doc/lightning-hsmtool.8.md b/doc/lightning-hsmtool.8.md index c87c2c449..3920fd328 100644 --- a/doc/lightning-hsmtool.8.md +++ b/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 ---- diff --git a/tools/hsmtool.c b/tools/hsmtool.c index b795c3cab..8091ac181 100644 --- a/tools/hsmtool.c +++ b/tools/hsmtool.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -13,15 +14,19 @@ #include #include #include +#include #include #include +#include #include +#include #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) " [hsm_secret password]\n"); printf(" - guesstoremote " " [hsm_secret password]\n"); + printf(" - generatehsm \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), ¤t_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, ¤t_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]); }