diff --git a/doc/lightning-hsmtool.8.md b/doc/lightning-hsmtool.8.md index d10f7b3b3ee6..0a9b3c70b191 100644 --- a/doc/lightning-hsmtool.8.md +++ b/doc/lightning-hsmtool.8.md @@ -53,8 +53,8 @@ and is usually no greater than the number of channels that the node has ever had. Specify *password* if the `hsm_secret_path` is encrypted. -**generatehsm** *hsm\_secret\_path* - Generates a new hsm\_secret using BIP39. +**generatehsm** *hsm\_secret\_path* \[*lang* *seed\_phrase* \[*passphrase*\]\] + Generates a new hsm\_secret using BIP39. If lang, seed\_phrase and optional passphrase are not provided they will be prompted for. *lang* can be "en" (English), "es" (Spanish), "fr" (French), "it" ("Italian"), "jp" (Japanese), "zhs" (Chinese Simplified) or "zht" ("Chinese Traditional"). Note that the seed phrase consists of multiple words, so should be surrounded by quotes. **checkhsm** *hsm\_secret\_path* Checks that hsm\_secret matches a BIP39 passphrase. @@ -117,4 +117,3 @@ COPYING 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: - diff --git a/tests/test_wallet.py b/tests/test_wallet.py index cf197fdc0fc4..fee82dc0574c 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -1383,6 +1383,27 @@ def test_hsmtool_generatehsm(node_factory): # We can start the node with this hsm_secret l1.start() assert l1.info['id'] == '02244b73339edd004bc6dfbb953a87984c88e9e7c02ca14ef6ec593ca6be622ba7' + l1.stop() + + # We can do the entire thing non-interactive! + os.remove(hsm_path) + subprocess.check_output(["tools/hsmtool", + "generatehsm", hsm_path, + "en", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"]) + assert open(hsm_path, "rb").read().hex() == "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc1" + + # Including passphrase + os.remove(hsm_path) + subprocess.check_output(["tools/hsmtool", + "generatehsm", hsm_path, + "en", + "ritual idle hat sunny universe pluck key alpha wing cake have wedding", + "This is actually not a passphrase"]) + + l1.start() + assert l1.info['id'] == '02244b73339edd004bc6dfbb953a87984c88e9e7c02ca14ef6ec593ca6be622ba7' + l1.stop() # this test does a 'listtransactions' on a yet unconfirmed channel diff --git a/tools/hsmtool.c b/tools/hsmtool.c index 228b2e17ad22..9ae8f7476167 100644 --- a/tools/hsmtool.c +++ b/tools/hsmtool.c @@ -43,7 +43,7 @@ static void show_usage(const char *progname) "\n"); printf(" - guesstoremote " "\n"); - printf(" - generatehsm \n"); + printf(" - generatehsm [ []]\n"); printf(" - checkhsm \n"); printf(" - dumponchaindescriptors [--show-secrets] [network]\n"); printf(" - makerune \n"); @@ -450,21 +450,31 @@ static int guess_to_remote(const char *address, struct node_id *node_id, return 1; } +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"}, +}; + +static bool check_lang(const char *abbr) +{ + for (size_t i = 0; i < ARRAY_SIZE(languages); i++) { + if (streq(abbr, languages[i].abbr)) + return true; + } + return false; +} + 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++) { @@ -490,7 +500,7 @@ static void get_words(struct words **words) { bip39_get_wordlist(languages[val].abbr, words); } -static void get_mnemonic(char *mnemonic) { +static char *get_mnemonic(void) { char *line = NULL; size_t line_size = 0; @@ -500,42 +510,53 @@ static void get_mnemonic(char *mnemonic) { if (characters < 0) errx(ERROR_USAGE, "Could not read line from stdin."); line[characters-1] = '\0'; - strcpy(mnemonic, line); - free(line); + return line; } -static void read_mnemonic(char *mnemonic) { +static char *read_mnemonic(void) { /* Get words for the mnemonic language */ struct words *words; get_words(&words); /* Get mnemonic */ - get_mnemonic(mnemonic); + char *mnemonic; + mnemonic = get_mnemonic(); if (bip39_mnemonic_validate(words, mnemonic) != 0) { errx(ERROR_USAGE, "Invalid mnemonic: \"%s\"", mnemonic); } + return mnemonic; } -static int generate_hsm(const char *hsm_secret_path) +static int generate_hsm(const char *hsm_secret_path, + const char *lang_id, + const char *mnemonic, + char *passphrase) { - char mnemonic[BIP39_WORDLIST_LEN]; - char *passphrase; const char *err; int exit_code = 0; - read_mnemonic(mnemonic); - printf("Warning: remember that different passphrases yield different " - "bitcoin wallets.\n"); - printf("If left empty, no password is used (echo is disabled).\n"); - printf("Enter your passphrase: \n"); - fflush(stdout); - passphrase = read_stdin_pass_with_exit_code(&err, &exit_code); - if (!passphrase) - errx(exit_code, "%s", err); - if (strlen(passphrase) == 0) { - free(passphrase); - passphrase = NULL; + if (lang_id == NULL) { + mnemonic = read_mnemonic(); + printf("Warning: remember that different passphrases yield different " + "bitcoin wallets.\n"); + printf("If left empty, no password is used (echo is disabled).\n"); + printf("Enter your passphrase: \n"); + fflush(stdout); + passphrase = read_stdin_pass_with_exit_code(&err, &exit_code); + if (!passphrase) + errx(exit_code, "%s", err); + if (strlen(passphrase) == 0) { + free(passphrase); + passphrase = NULL; + } + } else { + struct words *words; + + bip39_get_wordlist(lang_id, &words); + + if (bip39_mnemonic_validate(words, mnemonic) != 0) + errx(ERROR_USAGE, "Invalid mnemonic: \"%s\"", mnemonic); } u8 bip32_seed[BIP39_SEED_LEN_512]; @@ -631,7 +652,7 @@ static int dumponchaindescriptors(const char *hsm_secret_path, static int check_hsm(const char *hsm_secret_path) { - char mnemonic[BIP39_WORDLIST_LEN]; + char *mnemonic; struct secret hsm_secret; u8 bip32_seed[BIP39_SEED_LEN_512]; size_t bip32_seed_len; @@ -654,7 +675,7 @@ static int check_hsm(const char *hsm_secret_path) passphrase = NULL; } - read_mnemonic(mnemonic); + mnemonic = read_mnemonic(); 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"); @@ -772,10 +793,12 @@ int main(int argc, char *argv[]) } if (streq(method, "generatehsm")) { - if (argc != 3) + // argv[2] file, argv[3] lang_id, argv[4] word list, argv[5] passphrase + if (argc < 3 || argc > 6 || argc == 4) show_usage(argv[0]); char *hsm_secret_path = argv[2]; + char *lang_id, *word_list, *passphrase; /* if hsm_secret already exists we abort the process * we do not want to lose someone else's funds */ @@ -783,7 +806,15 @@ int main(int argc, char *argv[]) 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); + lang_id = (argc > 3 ? argv[3] : NULL); + if (lang_id && !check_lang(lang_id)) + show_usage(argv[0]); + + word_list = (argc > 4 ? argv[4] : NULL); + /* generate_hsm expects to free this, so use strdup */ + passphrase = (argc > 5 ? strdup(argv[5]) : NULL); + + return generate_hsm(hsm_secret_path, lang_id, word_list, passphrase); } if (streq(method, "dumponchaindescriptors")) {