Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hsmtool.c - Added new method to enable creation of hsm_file from cmd-line args #7102

Merged
merged 3 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions doc/lightning-hsmtool.8.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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: <https://github.com/ElementsProject/lightning>

21 changes: 21 additions & 0 deletions tests/test_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Comment on lines +1386 to +1394
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was more happy to have a separate test, but anyway 😄


# 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
Expand Down
109 changes: 70 additions & 39 deletions tools/hsmtool.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ static void show_usage(const char *progname)
"<path/to/hsm_secret>\n");
printf(" - guesstoremote <P2WPKH address> <node id> <tries> "
"<path/to/hsm_secret>\n");
printf(" - generatehsm <path/to/new/hsm_secret>\n");
printf(" - generatehsm <path/to/new/hsm_secret> [<language_id> <word list> [<password>]]\n");
printf(" - checkhsm <path/to/new/hsm_secret>\n");
printf(" - dumponchaindescriptors [--show-secrets] <path/to/hsm_secret> [network]\n");
printf(" - makerune <path/to/hsm_secret>\n");
Expand Down Expand Up @@ -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++) {
Expand All @@ -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;

Expand All @@ -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];
Expand Down Expand Up @@ -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;
Expand All @@ -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");

Expand Down Expand Up @@ -772,18 +793,28 @@ 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 */
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);
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")) {
Expand Down
Loading