tools: update hsmtool to use the new hsm_secret API.
What changed, and why it matters
This commit refactors the Core Lightning 'hsmtool' utility to use a new unified API for handling the node's master secret file (hsm_secret). It adds support for storing the secret as a 12-word English mnemonic phrase inside the file, and removes support for non-English mnemonic wordlists. The change is a feature/rewrite rather than a clear security fix, but it touches sensitive key-handling code and removes some legacy behavior. There is no vendor statement that this is a security patch, and no independent attribution.
Review the new hsm_secret API implementation (not fully shown in this diff) for correct passphrase handling, memory zeroization, and file permissions. Verify that storing the mnemonic in the hsm_secret file is an intentional design choice and that the seed hash comparison does not introduce false positives/negatives. Re-enable the skipped expose-secrets test once the unrelated issue is resolved. Treat as a normal feature commit unless additional context shows it fixes a disclosed vulnerability.
Security signals we found
Refactor of secret-key handling utility
New mnemonic storage format stores seed hash plus cleartext mnemonic in hsm_secret file
Removal of non-English BIP39 language support reduces attack surface but may break existing workflows
Legacy encrypt/decrypt commands now explicitly reject mnemonic-format files
Temporary skip of test_exposesecret (reason unrelated to patch content)
No explicit security bug fix or CVE reference in commit message
Evidence from the diff
The patch migrates tools/hsmtool.c from the old hsm_encryption.h API to a new common/hsm_secret.h API. It introduces a unified load_hsm_secret() helper that auto-detects file format (plain 32-byte, encrypted 73-byte, or mnemonic) and prompts for a passphrase when needed. New helper functions format_type_name() and grab_file_contents() are added in common/hsm_secret.c. The generatehsm command now writes a mnemonic directly into hsm_secret (32-byte seed hash + mnemonic text) instead of only a 32-byte binary seed, and only supports English BIP39 wordlists. encrypt/decrypt are now labeled ‘LEGACY’ and restricted to the old binary formats. Tests are heavily rewritten to cover mnemonic generation, passphrase handling, and legacy compatibility. One unrelated test (test_exposesecret) is temporarily skipped.
Changed components
tools/hsmtool.ccommon/hsm_secret.ccommon/hsm_secret.htests/test_wallet.pytests/test_plugin.pyInspect captured patch +609 / −657
diff --git a/common/hsm_secret.c b/common/hsm_secret.c
index e7678dcb..1dfe2b9a 100644
--- a/common/hsm_secret.c
+++ b/common/hsm_secret.c
@@ -1,6 +1,7 @@
#include "config.h"
#include <assert.h>
#include <ccan/mem/mem.h>
+#include <ccan/tal/grab_file/grab_file.h>
#include <ccan/tal/str/str.h>
#include <common/errcode.h>
#include <common/hsm_secret.h>
@@ -459,3 +460,37 @@ int is_legacy_hsm_secret_encrypted(const char *path)
return st.st_size == ENCRYPTED_HSM_SECRET_LEN;
}
+
+const char *format_type_name(enum hsm_secret_type type)
+{
+ switch (type) {
+ case HSM_SECRET_PLAIN:
+ return "plain (32-byte binary)";
+ case HSM_SECRET_ENCRYPTED:
+ return "encrypted (73-byte binary)";
+ case HSM_SECRET_MNEMONIC_NO_PASS:
+ return "mnemonic (no password)";
+ case HSM_SECRET_MNEMONIC_WITH_PASS:
+ return "mnemonic (with password)";
+ case HSM_SECRET_INVALID:
+ return "invalid";
+ }
+ return "unknown";
+}
+
+u8 *grab_file_contents(const tal_t *ctx, const char *filename, size_t *len)
+{
+ u8 *contents = grab_file(ctx, filename);
+ if (!contents) {
+ if (len)
+ *len = 0;
+ return NULL;
+ }
+
+ /* grab_file adds a NUL terminator, so we resize to remove it */
+ size_t contents_len = tal_bytelen(contents) - 1;
+ if (len)
+ *len = contents_len;
+
+ return contents;
+}
diff --git a/common/hsm_secret.h b/common/hsm_secret.h
index 0f32c233..4fca3a48 100644
--- a/common/hsm_secret.h
+++ b/common/hsm_secret.h
@@ -149,4 +149,33 @@ int is_legacy_hsm_secret_encrypted(const char *path);
*/
void destroy_secret(struct secret *secret);
+/**
+ * Convert hsm_secret_type enum to human-readable string.
+ * @type - the hsm_secret_type to convert
+ *
+ * Returns a string describing the type.
+ */
+const char *format_type_name(enum hsm_secret_type type);
+
+/**
+ * Wrapper around grab_file that removes the NUL terminator.
+ * @ctx - tal context for allocation
+ * @filename - path to the file to read
+ * @len - output parameter for the file length (excluding NUL terminator)
+ *
+ * Returns file contents with NUL terminator removed, or NULL on error.
+ * Unlike grab_file, the returned data does not include the NUL terminator.
+ */
+u8 *grab_file_contents(const tal_t *ctx, const char *filename, size_t *len);
+
+/**
+ * Derive encryption key from passphrase using Argon2.
+ * @ctx - tal context for allocation
+ * @passphrase - the passphrase to derive from
+ *
+ * Returns derived encryption key, or NULL on error.
+ * The returned key is memory-locked and has a destructor to clear it.
+ */
+struct secret *get_encryption_key(const tal_t *ctx, const char *passphrase);
+
#endif /* LIGHTNING_COMMON_HSM_SECRET_H */
diff --git a/tests/test_plugin.py b/tests/test_plugin.py
index a9345140..fef0c8ab 100644
--- a/tests/test_plugin.py
+++ b/tests/test_plugin.py
@@ -4319,6 +4319,7 @@ def test_important_plugin_shutdown(node_factory):
l1.rpc.plugin_start(os.path.join(os.getcwd(), 'plugins/pay'))
+@pytest.mark.skip(reason="Temporarily disabled expose secrets test")
@unittest.skipIf(VALGRIND, "It does not play well with prompt and key derivation.")
def test_exposesecret(node_factory):
l1, l2 = node_factory.get_nodes(2, opts=[{'exposesecret-passphrase': "test_exposesecret"}, {}])
diff --git a/tests/test_wallet.py b/tests/test_wallet.py
index dbe1e09c..a1267e4e 100644
--- a/tests/test_wallet.py
+++ b/tests/test_wallet.py
@@ -2,7 +2,6 @@ from bitcoin.rpc import JSONRPCError
from decimal import Decimal
from fixtures import * # noqa: F401,F403
from fixtures import TEST_NETWORK
-from pathlib import Path
from pyln.client import RpcError, Millisatoshi
from utils import (
only_one, wait_for, sync_blockheight,
@@ -1312,47 +1311,23 @@ class HsmTool(TailableProc):
@unittest.skipIf(VALGRIND, "It does not play well with prompt and key derivation.")
def test_hsmtool_secret_decryption(node_factory):
- l1 = node_factory.get_node()
- password = "reckless123#{ù}\n"
+ """Test that we can encrypt and decrypt hsm_secret using hsmtool"""
+ l1 = node_factory.get_node(start=False) # Don't start the node
+ password = "test_password\n"
hsm_path = os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, "hsm_secret")
- # We need to simulate a terminal to use termios in `lightningd`.
- master_fd, slave_fd = os.openpty()
- # Encrypt the master seed
- l1.stop()
- l1.daemon.opts.update({"encrypted-hsm": None})
- l1.daemon.start(stdin=slave_fd, wait_for_initialized=False)
- l1.daemon.wait_for_log(r'Enter hsm_secret password')
- write_all(master_fd, password.encode("utf-8"))
- l1.daemon.wait_for_log(r'Confirm hsm_secret password')
- write_all(master_fd, password.encode("utf-8"))
- l1.daemon.wait_for_log("Server started with public key")
- node_id = l1.rpc.getinfo()["id"]
- l1.stop()
+ # Write a known 32-byte key to hsm_secret
+ known_secret = b'\x01' * 32 # 32 bytes of 0x01
+ with open(hsm_path, 'wb') as f:
+ f.write(known_secret)
- # We can't use a wrong password !
- master_fd, slave_fd = os.openpty()
- hsmtool = HsmTool(node_factory.directory, "decrypt", hsm_path)
- hsmtool.start(stdin=slave_fd)
- hsmtool.wait_for_log(r"Enter hsm_secret password:")
- write_all(master_fd, "A wrong pass\n\n".encode("utf-8"))
- hsmtool.proc.wait(WAIT_TIMEOUT)
- hsmtool.is_in_log(r"Wrong password")
+ # Read the hsm_secret to verify it's what we expect
+ with open(hsm_path, 'rb') as f:
+ content = f.read()
+ assert content == known_secret, f"Expected {known_secret}, got {content}"
+ assert len(content) == 32, f"Expected 32 bytes, got {len(content)}"
- # Decrypt it with hsmtool
- master_fd, slave_fd = os.openpty()
- hsmtool.start(stdin=slave_fd)
- hsmtool.wait_for_log(r"Enter hsm_secret password:")
- write_all(master_fd, password.encode("utf-8"))
- assert hsmtool.proc.wait(WAIT_TIMEOUT) == 0
-
- # Then test we can now start it without password
- l1.daemon.opts.pop("encrypted-hsm")
- l1.daemon.start(stdin=slave_fd, wait_for_initialized=True)
- assert node_id == l1.rpc.getinfo()["id"]
- l1.stop()
-
- # Test we can encrypt it offline
+ # Encrypt it using hsmtool
master_fd, slave_fd = os.openpty()
hsmtool = HsmTool(node_factory.directory, "encrypt", hsm_path)
hsmtool.start(stdin=slave_fd)
@@ -1361,51 +1336,28 @@ def test_hsmtool_secret_decryption(node_factory):
hsmtool.wait_for_log(r"Confirm hsm_secret password:")
write_all(master_fd, password.encode("utf-8"))
assert hsmtool.proc.wait(WAIT_TIMEOUT) == 0
- # Now we need to pass the encrypted-hsm startup option
- l1.stop()
- with pytest.raises(subprocess.CalledProcessError, match=r'returned non-zero exit status {}'.format(HSM_ERROR_IS_ENCRYPT)):
- subprocess.check_call(l1.daemon.cmd_line)
-
- l1.daemon.opts.update({"encrypted-hsm": None})
- master_fd, slave_fd = os.openpty()
- l1.daemon.start(stdin=slave_fd,
- wait_for_initialized=False)
+ hsmtool.is_in_log(r"Successfully encrypted")
- l1.daemon.wait_for_log(r'The hsm_secret is encrypted')
- write_all(master_fd, password.encode("utf-8"))
- l1.daemon.wait_for_log("Server started with public key")
- print(node_id, l1.rpc.getinfo()["id"])
- assert node_id == l1.rpc.getinfo()["id"]
- l1.stop()
+ # Read the hsm_secret again - it should now be encrypted (73 bytes)
+ with open(hsm_path, 'rb') as f:
+ encrypted_content = f.read()
+ assert len(encrypted_content) == 73, f"Expected 73 bytes after encryption, got {len(encrypted_content)}"
+ assert encrypted_content != known_secret, "File should be encrypted and different from original"
- # And finally test that we can also decrypt if encrypted with hsmtool
+ # Decrypt it using hsmtool
master_fd, slave_fd = os.openpty()
hsmtool = HsmTool(node_factory.directory, "decrypt", hsm_path)
hsmtool.start(stdin=slave_fd)
hsmtool.wait_for_log(r"Enter hsm_secret password:")
write_all(master_fd, password.encode("utf-8"))
assert hsmtool.proc.wait(WAIT_TIMEOUT) == 0
- l1.daemon.opts.pop("encrypted-hsm")
- l1.daemon.start(stdin=slave_fd, wait_for_initialized=True)
- assert node_id == l1.rpc.getinfo()["id"]
+ hsmtool.is_in_log(r"Successfully decrypted")
- # We can roundtrip encryption and decryption using a password provided
- # through stdin.
- hsmtool = HsmTool(node_factory.directory, "encrypt", hsm_path)
- hsmtool.start(stdin=subprocess.PIPE)
- hsmtool.proc.stdin.write(password.encode("utf-8"))
- hsmtool.proc.stdin.write(password.encode("utf-8"))
- hsmtool.proc.stdin.flush()
- hsmtool.wait_for_log("Successfully encrypted")
- assert hsmtool.proc.wait(WAIT_TIMEOUT) == 0
-
- master_fd, slave_fd = os.openpty()
- hsmtool = HsmTool(node_factory.directory, "decrypt", hsm_path)
- hsmtool.start(stdin=slave_fd)
- hsmtool.wait_for_log("Enter hsm_secret password:")
- write_all(master_fd, password.encode("utf-8"))
- hsmtool.wait_for_log("Successfully decrypted")
- assert hsmtool.proc.wait(WAIT_TIMEOUT) == 0
+ # Read the hsm_secret again - it should now be back to the original 32 bytes
+ with open(hsm_path, 'rb') as f:
+ decrypted_content = f.read()
+ assert decrypted_content == known_secret, f"Expected {known_secret}, got {decrypted_content}"
+ assert len(decrypted_content) == 32, f"Expected 32 bytes after decryption, got {len(decrypted_content)}"
@unittest.skipIf(TEST_NETWORK == 'liquid-regtest', '')
@@ -1447,100 +1399,263 @@ def test_hsmtool_dump_descriptors(node_factory, bitcoind):
assert res["total_amount"] == Decimal('0.00001000')
-def test_hsmtool_generatehsm(node_factory):
- l1 = node_factory.get_node(start=False)
- hsm_path = os.path.join(l1.daemon.lightning_dir, TEST_NETWORK,
- "hsm_secret")
+@pytest.mark.parametrize("mnemonic,passphrase,expected_format", [
+ ("ritual idle hat sunny universe pluck key alpha wing cake have wedding", "test_passphrase", "mnemonic with passphrase"),
+ ("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", "", "mnemonic without passphrase"),
+])
+def test_hsmtool_generatehsm_variants(node_factory, mnemonic, passphrase, expected_format):
+ """Test generating mnemonic-based hsm_secret with various configurations"""
+ # Only set hsm-passphrase option if there's actually a passphrase
+ node_options = {'hsm-passphrase': None} if passphrase else {}
+ l1 = node_factory.get_node(start=False, options=node_options)
+ hsm_path = os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, "hsm_secret")
+ os.remove(hsm_path) # Remove the auto-generated one
+ # Generate hsm_secret with mnemonic and passphrase
hsmtool = HsmTool(node_factory.directory, "generatehsm", hsm_path)
-
- # You cannot re-generate an already existing hsm_secret
master_fd, slave_fd = os.openpty()
hsmtool.start(stdin=slave_fd)
- assert hsmtool.proc.wait(WAIT_TIMEOUT) == 2
+ hsmtool.wait_for_log(r"Introduce your BIP39 word list separated by space")
+ write_all(master_fd, f"{mnemonic}\n".encode("utf-8"))
+ hsmtool.wait_for_log(r"Enter your passphrase:")
+ write_all(master_fd, f"{passphrase}\n".encode("utf-8"))
+ assert hsmtool.proc.wait(WAIT_TIMEOUT) == 0
+ hsmtool.is_in_log(r"New hsm_secret file created")
+ hsmtool.is_in_log(f"Format: {expected_format}")
+
+ # Verify file format
+ with open(hsm_path, 'rb') as f:
+ content = f.read()
+ if passphrase:
+ # First 32 bytes should NOT be zeros (has passphrase hash)
+ assert content[:32] != b'\x00' * 32
+ assert mnemonic.encode('utf-8') in content[32:]
+ else:
+ # First 32 bytes should be zeros (no passphrase)
+ assert content[:32] == b'\x00' * 32
+ # Rest should be the mnemonic
+ mnemonic_part = content[32:].decode('utf-8')
+ assert mnemonic in mnemonic_part
+
+
+@pytest.mark.parametrize("test_case", [
+ pytest.param({
+ "name": "with_passphrase",
+ "mnemonic": "ritual idle hat sunny universe pluck key alpha wing cake have wedding",
+ "passphrase": "secret_passphrase",
+ "check_passphrase": "secret_passphrase",
+ "check_mnemonic": "ritual idle hat sunny universe pluck key alpha wing cake have wedding",
+ "expected_exit": 0,
+ "expected_log": "OK"
+ }, id="correct_mnemonic_with_passphrase"),
+ pytest.param({
+ "name": "no_passphrase",
+ "mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
+ "passphrase": "",
+ "check_passphrase": "",
+ "check_mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
+ "expected_exit": 0,
+ "expected_log": "OK"
+ }, id="correct_mnemonic_no_passphrase"),
+ pytest.param({
+ "name": "wrong_passphrase",
+ "mnemonic": "ritual idle hat sunny universe pluck key alpha wing cake have wedding",
+ "passphrase": "correct_passphrase",
+ "check_passphrase": "wrong_passphrase",
+ "check_mnemonic": "ritual idle hat sunny universe pluck key alpha wing cake have wedding",
+ "expected_exit": 5, # ERROR_KEYDERIV
+ "expected_log": "resulting hsm_secret did not match"
+ }, id="wrong_passphrase_should_fail"),
+ pytest.param({
+ "name": "wrong_mnemonic",
+ "mnemonic": "ritual idle hat sunny universe pluck key alpha wing cake have wedding",
+ "passphrase": "",
+ "check_passphrase": "",
+ "check_mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
+ "expected_exit": 5, # ERROR_KEYDERIV
+ "expected_log": "resulting hsm_secret did not match"
+ }, id="wrong_mnemonic_should_fail")
+])
+def test_hsmtool_checkhsm_variants(node_factory, test_case):
+ """Test checkhsm with various configurations"""
+ l1 = node_factory.get_node(start=False)
+ hsm_path = os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, "hsm_secret")
os.remove(hsm_path)
- # We can generate a valid hsm_secret from a wordlist and a "passphrase"
+ # Create hsm_secret with known mnemonic and passphrase
+ hsmtool = HsmTool(node_factory.directory, "generatehsm", hsm_path)
master_fd, slave_fd = os.openpty()
hsmtool.start(stdin=slave_fd)
- hsmtool.wait_for_log(r"Select your language:")
- write_all(master_fd, "0\n".encode("utf-8"))
- hsmtool.wait_for_log(r"Introduce your BIP39 word list")
- write_all(master_fd, "ritual idle hat sunny universe pluck key alpha wing "
- "cake have wedding\n".encode("utf-8"))
+ hsmtool.wait_for_log(r"Introduce your BIP39 word list separated by space")
+ write_all(master_fd, f"{test_case['mnemonic']}\n".encode("utf-8"))
hsmtool.wait_for_log(r"Enter your passphrase:")
- write_all(master_fd, "This is actually not a passphrase\n".encode("utf-8"))
- if hsmtool.proc.wait(WAIT_TIMEOUT) != 0:
- hsmtool.logs_catchup()
- print("hsmtool failure! Logs:")
- for l in hsmtool.logs:
- print(' ' + l)
- assert False
- hsmtool.is_in_log(r"New hsm_secret file created")
+ write_all(master_fd, f"{test_case['passphrase']}\n".encode("utf-8"))
+ assert hsmtool.proc.wait(WAIT_TIMEOUT) == 0
- # Check should pass.
+ # Test checkhsm with credentials
hsmtool = HsmTool(node_factory.directory, "checkhsm", hsm_path)
master_fd, slave_fd = os.openpty()
hsmtool.start(stdin=slave_fd)
- hsmtool.wait_for_log(r"Enter your passphrase:")
- write_all(master_fd, "This is actually not a passphrase\n".encode("utf-8"))
- hsmtool.wait_for_log(r"Select your language:")
- write_all(master_fd, "0\n".encode("utf-8"))
- hsmtool.wait_for_log(r"Introduce your BIP39 word list")
- write_all(master_fd, "ritual idle hat sunny universe pluck key alpha wing "
- "cake have wedding\n".encode("utf-8"))
+
+ # If the original had a passphrase, we need to unlock the file first
+ if test_case['passphrase']:
+ hsmtool.wait_for_log(r"Enter hsm_secret password:") # Decrypt file
+ write_all(master_fd, f"{test_case['passphrase']}\n".encode("utf-8"))
+
+ hsmtool.wait_for_log(r"Enter your mnemonic passphrase:") # Backup verification
+ write_all(master_fd, f"{test_case['check_passphrase']}\n".encode("utf-8"))
+ hsmtool.wait_for_log(r"Introduce your BIP39 word list separated by space")
+ write_all(master_fd, f"{test_case['check_mnemonic']}\n".encode("utf-8"))
+ assert hsmtool.proc.wait(WAIT_TIMEOUT) == test_case['expected_exit']
+ hsmtool.is_in_log(test_case['expected_log'])
+
+
+def test_hsmtool_checkhsm_legacy_encrypted_with_mnemonic_no_passphrase(node_factory):
+ """Test checkhsm with legacy encrypted hsm_secret containing mnemonic without passphrase"""
+ l1 = node_factory.get_node(start=False)
+ hsm_path = os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, "hsm_secret")
+ os.remove(hsm_path)
+ seed_hex = "31bb58d1180831868fd5f562bb74659dca1e9673d034af635df53d677b9e5f03"
+ seed_bytes = bytes.fromhex(seed_hex)
+
+ # Write the 32-byte seed directly to file (simulating old generatehsm output)
+ # Make sure we write exactly 32 bytes with no newline
+ assert len(seed_bytes) == 32, f"Seed should be exactly 32 bytes, got {len(seed_bytes)}"
+ with open(hsm_path, 'wb') as f:
+ f.write(seed_bytes)
+
+ # Verify it's exactly 32 bytes
+ with open(hsm_path, 'rb') as f:
+ content = f.read()
+ print(content)
+ assert content == seed_bytes, "File content doesn't match expected seed"
+
+ # Now encrypt it using the legacy encrypt command
+ encryption_password = "encryption_password"
+ hsmtool = HsmTool(node_factory.directory, "encrypt", hsm_path)
+ master_fd, slave_fd = os.openpty()
+ hsmtool.start(stdin=slave_fd)
+ hsmtool.wait_for_log(r"Enter hsm_secret password:")
+ write_all(master_fd, f"{encryption_password}\n".encode("utf-8"))
+ hsmtool.wait_for_log(r"Confirm hsm_secret password:")
+ write_all(master_fd, f"{encryption_password}\n".encode("utf-8"))
+ assert hsmtool.proc.wait(WAIT_TIMEOUT) == 0
+ hsmtool.is_in_log(r"Successfully encrypted")
+
+ # Verify the file is now encrypted (73 bytes)
+ with open(hsm_path, 'rb') as f:
+ content = f.read()
+ assert len(content) == 73, f"Expected 73 bytes after encryption, got {len(content)}"
+
+ # Test checkhsm - should prompt for encryption password first, then mnemonic passphrase
+ hsmtool = HsmTool(node_factory.directory, "checkhsm", hsm_path)
+ master_fd, slave_fd = os.openpty()
+ hsmtool.start(stdin=slave_fd)
+ hsmtool.wait_for_log(r"Enter hsm_secret password:") # Encryption password
+ write_all(master_fd, f"{encryption_password}\n".encode("utf-8"))
+ hsmtool.wait_for_log(r"Enter your mnemonic passphrase:") # Mnemonic passphrase (empty)
+ write_all(master_fd, "\n".encode("utf-8")) # Empty passphrase
+ hsmtool.wait_for_log(r"Introduce your BIP39 word list separated by space")
+ write_all(master_fd, "blame expire peanut sell door zoo bundle motor truth outside artist siren\n".encode("utf-8"))
assert hsmtool.proc.wait(WAIT_TIMEOUT) == 0
hsmtool.is_in_log(r"OK")
- # Wrong mnemonic will fail.
+
+def test_hsmtool_checkhsm_legacy_encrypted_with_mnemonic_passphrase(node_factory):
+ """Test checkhsm with legacy encrypted hsm_secret containing mnemonic with passphrase"""
+ l1 = node_factory.get_node(start=False)
+ hsm_path = os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, "hsm_secret")
+ os.remove(hsm_path)
+
+ # Directly write the 32-byte seed from mnemonic with passphrase
+ # Mnemonic: "blame expire peanut sell door zoo bundle motor truth outside artist siren"
+ # Passphrase: "passphrase"
+ # Expected BIP39 seed (first 32 bytes): 161d740bcfd3c5e2a1769159bee86868ab35e7544e83e825042a43b929ad950c
+ seed_hex = "161d740bcfd3c5e2a1769159bee86868ab35e7544e83e825042a43b929ad950c"
+ seed_bytes = bytes.fromhex(seed_hex)
+
+ # Write the 32-byte seed directly to file (simulating old generatehsm output)
+ with open(hsm_path, 'wb') as f:
+ f.write(seed_bytes)
+
+ # Verify it's 32 bytes
+ with open(hsm_path, 'rb') as f:
+ content = f.read()
+ assert len(content) == 32, f"Expected 32 bytes, got {len(content)}"
+
+ # Now encrypt it using the legacy encrypt command
+ encryption_password = "encryption_password"
+ hsmtool = HsmTool(node_factory.directory, "encrypt", hsm_path)
master_fd, slave_fd = os.openpty()
hsmtool.start(stdin=slave_fd)
- hsmtool.wait_for_log(r"Enter your passphrase:")
- write_all(master_fd, "This is actually not a passphrase\n".encode("utf-8"))
- hsmtool.wait_for_log(r"Select your language:")
- write_all(master_fd, "0\n".encode("utf-8"))
- hsmtool.wait_for_log(r"Introduce your BIP39 word list")
- write_all(master_fd, "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about\n".encode("utf-8"))
- assert hsmtool.proc.wait(WAIT_TIMEOUT) == 5
- hsmtool.is_in_log(r"resulting hsm_secret did not match")
+ hsmtool.wait_for_log(r"Enter hsm_secret password:")
+ write_all(master_fd, f"{encryption_password}\n".encode("utf-8"))
+ hsmtool.wait_for_log(r"Confirm hsm_secret password:")
+ write_all(master_fd, f"{encryption_password}\n".encode("utf-8"))
+ assert hsmtool.proc.wait(WAIT_TIMEOUT) == 0
+ hsmtool.is_in_log(r"Successfully encrypted")
- # Wrong passphrase will fail.
+ # Verify the file is now encrypted (73 bytes)
+ with open(hsm_path, 'rb') as f:
+ content = f.read()
+ assert len(content) == 73, f"Expected 73 bytes after encryption, got {len(content)}"
+
+ # Test checkhsm - should prompt for encryption password first, then mnemonic passphrase
+ hsmtool = HsmTool(node_factory.directory, "checkhsm", hsm_path)
master_fd, slave_fd = os.openpty()
hsmtool.start(stdin=slave_fd)
- hsmtool.wait_for_log(r"Enter your passphrase:")
- write_all(master_fd, "This is actually not a passphrase \n".encode("utf-8"))
- hsmtool.wait_for_log(r"Select your language:")
- write_all(master_fd, "0\n".encode("utf-8"))
- hsmtool.wait_for_log(r"Introduce your BIP39 word list")
- write_all(master_fd, "ritual idle hat sunny universe pluck key alpha wing "
- "cake have wedding\n".encode("utf-8"))
- assert hsmtool.proc.wait(WAIT_TIMEOUT) == 5
- hsmtool.is_in_log(r"resulting hsm_secret did not match")
+ hsmtool.wait_for_log(r"Enter hsm_secret password:") # Encryption password
+ write_all(master_fd, f"{encryption_password}\n".encode("utf-8"))
+ hsmtool.wait_for_log(r"Enter your mnemonic passphrase:") # Mnemonic passphrase
+ write_all(master_fd, "passphrase\n".encode("utf-8"))
+ hsmtool.wait_for_log(r"Introduce your BIP39 word list separated by space")
+ write_all(master_fd, "blame expire peanut sell door zoo bundle motor truth outside artist siren\n".encode("utf-8"))
+ assert hsmtool.proc.wait(WAIT_TIMEOUT) == 0
+ hsmtool.is_in_log(r"OK")
- # 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 Path(hsm_path).read_bytes().hex() == "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc1"
+def test_hsmtool_generatehsm_file_exists_error(node_factory):
+ """Test that generatehsm fails if file already exists"""
+ l1 = node_factory.get_node(start=False)
+ hsm_path = os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, "hsm_secret")
+
+ # File already exists from node creation
+ hsmtool = HsmTool(node_factory.directory, "generatehsm", hsm_path)
+ master_fd, slave_fd = os.openpty()
+ hsmtool.start(stdin=slave_fd)
+ assert hsmtool.proc.wait(WAIT_TIMEOUT) == 2 # ERROR_USAGE
+ hsmtool.is_in_log(r"hsm_secret file.*already exists")
- # Including passphrase
+
+def test_hsmtool_all_commands_work_with_mnemonic_formats(node_factory):
+ """Test that all hsmtool commands work with mnemonic formats"""
+ l1 = node_factory.get_node(start=False)
+ hsm_path = os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, "hsm_secret")
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()
+ # Create a mnemonic-based hsm_secret (no passphrase for simplicity)
+ hsmtool = HsmTool(node_factory.directory, "generatehsm", hsm_path)
+ master_fd, slave_fd = os.openpty()
+ hsmtool.start(stdin=slave_fd)
+ hsmtool.wait_for_log(r"Introduce your BIP39 word list")
+ write_all(master_fd, "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about\n".encode("utf-8"))
+ hsmtool.wait_for_log(r"Enter your passphrase:")
+ write_all(master_fd, "\n".encode("utf-8"))
+ assert hsmtool.proc.wait(WAIT_TIMEOUT) == 0
+
+ # Test various commands work with mnemonic format
+ test_commands = [
+ (["getnodeid", hsm_path], "03653e90c1ce4660fd8505dd6d643356e93cfe202af109d382787639dd5890e87d"),
+ (["getcodexsecret", hsm_path, "test"], "cl10testst6cqh0wu7p5ssjyf4z4ez42ks9jlt3zneju9uuypr2hddak6tlqsghuxusm6m6azq"),
+ (["makerune", hsm_path], "6VkrWMI2hm2a2UTkg-EyUrrBJN0RcuPB80I1pCVkTD89MA=="),
+ (["dumponchaindescriptors", hsm_path], "wpkh(xpub661MyMwAqRbcG9kjo3mdWQuSDbtdJzsd3K2mvifyeUMF3GhLcBAfELqjuxCvxUkYqQVe6rJ9SzmpipoUedb5MD79MJaLL8RME2A3J3Fw6Zd/0/0/*)#2jtshmk0\nsh(wpkh(xpub661MyMwAqRbcG9kjo3mdWQuSDbtdJzsd3K2mvifyeUMF3GhLcBAfELqjuxCvxUkYqQVe6rJ9SzmpipoUedb5MD79MJaLL8RME2A3J3Fw6Zd/0/0/*))#u6am4was\ntr(xpub661MyMwAqRbcG9kjo3mdWQuSDbtdJzsd3K2mvifyeUMF3GhLcBAfELqjuxCvxUkYqQVe6rJ9SzmpipoUedb5MD79MJaLL8RME2A3J3Fw6Zd/0/0/*)#v9hf4756"),
+ ]
+
+ for cmd_args, expected_output in test_commands:
+ cmd_line = ["tools/hsmtool"] + cmd_args
+ out = subprocess.check_output(cmd_line).decode("utf8")
+ actual_output = out.strip()
+ assert actual_output == expected_output, f"Command {cmd_args[0]} output mismatch"
# this test does a 'listtransactions' on a yet unconfirmed channel
@@ -1828,39 +1943,6 @@ def test_upgradewallet(node_factory, bitcoind):
assert upgrade['upgraded_outs'] == 0
-def test_hsmtool_makerune(node_factory):
- """Test we can make a valid rune before the node really exists"""
- l1 = node_factory.get_node(start=False, options={
- 'allow-deprecated-apis': True,
- })
-
- # get_node() creates a secret, but in usual case we generate one.
- hsm_path = os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, "hsm_secret")
- os.remove(hsm_path)
-
- hsmtool = HsmTool(node_factory.directory, "generatehsm", hsm_path)
- master_fd, slave_fd = os.openpty()
- hsmtool.start(stdin=slave_fd)
- hsmtool.wait_for_log(r"Select your language:")
- write_all(master_fd, "0\n".encode("utf-8"))
- hsmtool.wait_for_log(r"Introduce your BIP39 word list")
- write_all(master_fd, "ritual idle hat sunny universe pluck key alpha wing "
- "cake have wedding\n".encode("utf-8"))
- hsmtool.wait_for_log(r"Enter your passphrase:")
- write_all(master_fd, "This is actually not a passphrase\n".encode("utf-8"))
- assert hsmtool.proc.wait(WAIT_TIMEOUT) == 0
- hsmtool.is_in_log(r"New hsm_secret file created")
-
- cmd_line = ["tools/hsmtool", "makerune", hsm_path]
- out = subprocess.check_output(cmd_line).decode("utf8").split("\n")[0]
-
- l1.start()
-
- # We have to generate a rune now, for commando to even start processing!
- rune = l1.rpc.createrune()['rune']
- assert rune == out
-
-
def test_hsmtool_getnodeid(node_factory):
l1 = node_factory.get_node()
diff --git a/tools/hsmtool.c b/tools/hsmtool.c
index 1352bd80..129f8175 100644
--- a/tools/hsmtool.c
+++ b/tools/hsmtool.c
@@ -14,7 +14,7 @@
#include <common/derive_basepoints.h>
#include <common/descriptor_checksum.h>
#include <common/errcode.h>
-#include <common/hsm_encryption.h>
+#include <common/hsm_secret.h>
#include <common/key_derive.h>
#include <common/utils.h>
#include <common/utxo.h>
@@ -37,15 +37,15 @@ static void show_usage(const char *progname)
{
printf("%s <method> [arguments]\n", progname);
printf("methods:\n");
- printf(" - decrypt <path/to/hsm_secret>\n");
- printf(" - encrypt <path/to/hsm_secret>\n");
+ printf(" - decrypt <path/to/hsm_secret> [LEGACY - binary format only]\n");
+ printf(" - encrypt <path/to/hsm_secret> [LEGACY - binary format only]\n");
printf(" - dumpcommitments <node id> <channel dbid> <depth> "
- "<path/to/hsm_secret>\n");
+ "<path/to/hsm_secret>\n");
printf(" - guesstoremote <P2WPKH address> <node id> <tries> "
- "<path/to/hsm_secret>\n");
+ "<path/to/hsm_secret>\n");
+ printf(" - generatehsm <path/to/new/hsm_secret>\n");
printf(" - derivetoremote <node id> <channel dbid> [<cmt pt>] "
"<path/to/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");
@@ -77,165 +77,73 @@ static bool ensure_hsm_secret_exists(int fd, const char *path)
tal_free(config_dir);
return true;
}
-
-static void grab_hsm_file(const char *hsm_secret_path,
- void *dst, size_t dstlen)
-{
- u8 *contents = grab_file(tmpctx, hsm_secret_path);
- if (!contents)
- errx(EXITCODE_ERROR_HSM_FILE, "Reading hsm_secret");
-
- /* grab_file always appends a NUL char for convenience */
- if (tal_bytelen(contents) != dstlen + 1)
- errx(EXITCODE_ERROR_HSM_FILE,
- "hsm_secret invalid length %zu (expected %zu)",
- tal_bytelen(contents)-1, dstlen);
- memcpy(dst, contents, dstlen);
-}
-
-static void get_unencrypted_hsm_secret(struct secret *hsm_secret,
- const char *hsm_secret_path)
-{
- grab_hsm_file(hsm_secret_path, hsm_secret, sizeof(*hsm_secret));
-}
-
-/* Derive the encryption key from the password provided, and try to decrypt
- * the cipher. */
-static void get_encrypted_hsm_secret(struct secret *hsm_secret,
- const char *hsm_secret_path,
- const char *passwd)
+/* Load hsm_secret using the unified interface */
+static struct hsm_secret *load_hsm_secret(const tal_t *ctx, const char *hsm_secret_path)
{
- struct secret key;
- struct encrypted_hsm_secret encrypted_secret;
- const char *err;
- int exit_code;
-
- grab_hsm_file(hsm_secret_path,
- &encrypted_secret, sizeof(encrypted_secret));
-
- exit_code = hsm_secret_encryption_key_with_exitcode(passwd, &key, &err);
- if (exit_code > 0)
- errx(exit_code, "%s", err);
- if (!decrypt_hsm_secret(&key, &encrypted_secret, hsm_secret))
- errx(ERROR_LIBSODIUM, "Could not retrieve the seed. Wrong password ?");
-}
+ size_t contents_len;
+ u8 *contents = grab_file_contents(tmpctx, hsm_secret_path, &contents_len);
+ const char *passphrase = NULL;
+ struct hsm_secret *hsms;
+ enum hsm_secret_error error;
-/* Taken from hsmd. */
-static void get_channel_seed(struct secret *channel_seed, const struct node_id *peer_id,
- u64 dbid, struct secret *hsm_secret)
-{
- struct secret channel_base;
- u8 input[sizeof(peer_id->k) + sizeof(dbid)];
- /*~ Again, "per-peer" should be "per-channel", but Hysterical Raisins */
- const char *info = "per-peer seed";
+ if (!contents)
+ err(EXITCODE_ERROR_HSM_FILE, "Reading hsm_secret");
- /*~ We use the DER encoding of the pubkey, because it's platform
- * independent. Since the dbid is unique, however, it's completely
- * unnecessary, but again, existing users can't be broken. */
- /* FIXME: lnd has a nicer BIP32 method for deriving secrets which we
- * should migrate to. */
- hkdf_sha256(&channel_base, sizeof(struct secret), NULL, 0,
- hsm_secret, sizeof(*hsm_secret),
- /*~ Initially, we didn't support multiple channels per
- * peer at all: a channel had to be completely forgotten
- * before another could exist. That was slightly relaxed,
- * but the phrase "peer seed" is wired into the seed
- * generation here, so we need to keep it that way for
- * existing clients, rather than using "channel seed". */
- "peer seed", strlen("peer seed"));
- memcpy(input, peer_id->k, sizeof(peer_id->k));
- BUILD_ASSERT(sizeof(peer_id->k) == PUBKEY_CMPR_LEN);
- /*~ For all that talk about platform-independence, note that this
- * field is endian-dependent! But let's face it, little-endian won.
- * In related news, we don't support EBCDIC or middle-endian. */
- memcpy(input + PUBKEY_CMPR_LEN, &dbid, sizeof(dbid));
+ /* Get passphrase if needed */
+ if (hsm_secret_needs_passphrase(contents, contents_len)) {
+ printf("Enter hsm_secret password:\n");
+ fflush(stdout);
+ passphrase = read_stdin_pass(tmpctx, &error);
+ if (!passphrase)
+ errx(EXITCODE_ERROR_HSM_FILE, "Could not read password: %s", hsm_secret_error_str(error));
+ }
- hkdf_sha256(channel_seed, sizeof(*channel_seed),
- input, sizeof(input),
- &channel_base, sizeof(channel_base),
- info, strlen(info));
+ hsms = extract_hsm_secret(ctx, contents, contents_len, passphrase, &error);
+ if (!hsms) {
+ err(EXITCODE_ERROR_HSM_FILE, "%s", hsm_secret_error_str(error));
+ }
+ return hsms;
}
-/* We detect an encrypted hsm_secret as a hsm_secret which is 73-bytes long. */
-static bool hsm_secret_is_encrypted(const char *hsm_secret_path)
+/* Legacy function - only works with binary encrypted format */
+static void decrypt_hsm(const char *hsm_secret_path)
{
- switch (is_hsm_secret_encrypted(hsm_secret_path)) {
- case -1:
- err(EXITCODE_ERROR_HSM_FILE, "Cannot open '%s'", hsm_secret_path);
- case 1:
- return true;
- case 0: {
- /* Extra sanity check on HSM file! */
- struct stat st;
- stat(hsm_secret_path, &st);
- if (st.st_size != 32)
- errx(EXITCODE_ERROR_HSM_FILE,
- "Invalid hsm_secret '%s' (neither plaintext "
- "nor encrypted).", hsm_secret_path);
- return false;
- }
- }
+ int fd;
+ struct hsm_secret *hsms;
+ const char *dir, *backup;
- abort();
-}
+ /* Check if it's a format we can decrypt */
+ size_t contents_len;
+ u8 *contents = grab_file_contents(tmpctx, hsm_secret_path, &contents_len);
+ if (!contents)
+ err(EXITCODE_ERROR_HSM_FILE, "Reading hsm_secret");
-/* If encrypted, ask for a passphrase */
-static void get_hsm_secret(struct secret *hsm_secret,
- const char *hsm_secret_path)
-{
- /* This checks the file existence, too. */
- if (hsm_secret_is_encrypted(hsm_secret_path)) {
- int exit_code;
- char *passwd;
- const char *err;
+ enum hsm_secret_type type = detect_hsm_secret_type(contents, contents_len);
- printf("Enter hsm_secret password:\n");
- fflush(stdout);
- passwd = read_stdin_pass_with_exit_code(&err, &exit_code);
- if (!passwd)
- errx(exit_code, "%s", err);
- get_encrypted_hsm_secret(hsm_secret, hsm_secret_path, passwd);
- free(passwd);
- } else {
- get_unencrypted_hsm_secret(hsm_secret, hsm_secret_path);
+ if (type != HSM_SECRET_ENCRYPTED) {
+ errx(ERROR_USAGE, "decrypt command only works on legacy encrypted binary format (73 bytes).\n"
+ "Current file is: %s\n"
+ "For mnemonic formats, use the generatehsm command to create a new hsm_secret instead.",
+ format_type_name(type));
}
-}
-static int decrypt_hsm(const char *hsm_secret_path)
-{
- int fd;
- struct secret hsm_secret;
- char *passwd;
- const char *dir, *backup, *err;
- int exit_code = 0;
- /* This checks the file existence, too. */
- if (!hsm_secret_is_encrypted(hsm_secret_path))
- errx(ERROR_USAGE, "hsm_secret is not encrypted");
- printf("Enter hsm_secret password:\n");
- fflush(stdout);
- passwd = read_stdin_pass_with_exit_code(&err, &exit_code);
- if (!passwd)
- errx(exit_code, "%s", err);
+ /* Load the hsm_secret */
+ hsms = load_hsm_secret(tmpctx, hsm_secret_path);
dir = path_dirname(NULL, hsm_secret_path);
backup = path_join(dir, dir, "hsm_secret.backup");
- get_encrypted_hsm_secret(&hsm_secret, hsm_secret_path, passwd);
- /* Once the encryption key derived, we don't need it anymore. */
- if (passwd)
- free(passwd);
-
/* Create a backup file, "just in case". */
rename(hsm_secret_path, backup);
fd = open(hsm_secret_path, O_CREAT|O_EXCL|O_WRONLY, 0400);
if (fd < 0)
- errx(EXITCODE_ERROR_HSM_FILE, "Could not open new hsm_secret");
+ err(EXITCODE_ERROR_HSM_FILE, "Could not open new hsm_secret");
- if (!write_all(fd, &hsm_secret, sizeof(hsm_secret))) {
+ if (!write_all(fd, &hsms->secret, sizeof(hsms->secret))) {
unlink_noerr(hsm_secret_path);
close(fd);
rename("hsm_secret.backup", hsm_secret_path);
- errx(EXITCODE_ERROR_HSM_FILE,
+ err(EXITCODE_ERROR_HSM_FILE,
"Failure writing plaintext seed to hsm_secret.");
}
@@ -250,102 +158,78 @@ static int decrypt_hsm(const char *hsm_secret_path)
tal_free(dir);
printf("Successfully decrypted hsm_secret, be careful now :-).\n");
- return 0;
}
-static int make_codexsecret(const char *hsm_secret_path,
- const char *id)
+/* Legacy function - only works with binary plain format */
+static void encrypt_hsm(const char *hsm_secret_path)
{
- struct secret hsm_secret;
- char *bip93;
- const char *err;
- get_hsm_secret(&hsm_secret, hsm_secret_path);
-
- err = codex32_secret_encode(tmpctx, "cl", id, 0, hsm_secret.data, 32, &bip93);
- if (err)
- errx(ERROR_USAGE, "%s", err);
+ int fd;
+ struct hsm_secret *hsms;
+ u8 encrypted_hsm_secret[ENCRYPTED_HSM_SECRET_LEN];
+ const char *passwd, *passwd_confirmation;
+ const char *dir, *backup;
+ enum hsm_secret_error pass_err;
+
+ /* Check if it's a format we can encrypt */
+ size_t contents_len;
+ u8 *contents = grab_file_contents(tmpctx, hsm_secret_path, &contents_len);
+ if (!contents)
+ err(EXITCODE_ERROR_HSM_FILE, "Reading hsm_secret");
- printf("%s\n", bip93);
- return 0;
-}
+ enum hsm_secret_type type = detect_hsm_secret_type(contents, contents_len);
-static int getemergencyrecover(const char *emer_rec_path)
-{
- u8 *scb = grab_file(tmpctx, emer_rec_path);
- char *output, *hrp = "clnemerg";
- if (!scb) {
- errx(EXITCODE_ERROR_HSM_FILE, "Reading emergency.recover");
- } else {
- /* grab_file adds nul term */
- tal_resize(&scb, tal_bytelen(scb) - 1);
+ if (type != HSM_SECRET_PLAIN) {
+ errx(ERROR_USAGE, "encrypt command only works on legacy plain binary format (32 bytes).\n"
+ "Current file is: %s\n"
+ "For mnemonic formats, the passphrase is already integrated into the format.",
+ format_type_name(type));
}
- u5 *data = tal_arr(tmpctx, u5, 0);
-
- bech32_push_bits(&data, scb, tal_bytelen(scb) * 8);
- output = tal_arr(tmpctx, char, strlen(hrp) + tal_count(data) + 8);
-
- bech32_encode(output, hrp, data, tal_count(data), (size_t)-1,
- BECH32_ENCODING_BECH32);
- printf("%s\n", output);
- return 0;
-}
-
-static int encrypt_hsm(const char *hsm_secret_path)
-{
- int fd;
- struct secret key, hsm_secret;
- struct encrypted_hsm_secret encrypted_hsm_secret;
- char *passwd, *passwd_confirmation;
- const char *err, *dir, *backup;
- int exit_code = 0;
-
- /* This checks the file existence, too. */
- if (hsm_secret_is_encrypted(hsm_secret_path))
- errx(ERROR_USAGE, "hsm_secret is already encrypted");
+ /* Load the hsm_secret */
+ hsms = load_hsm_secret(tmpctx, hsm_secret_path);
printf("Enter hsm_secret password:\n");
fflush(stdout);
- passwd = read_stdin_pass_with_exit_code(&err, &exit_code);
+ passwd = read_stdin_pass(tmpctx, &pass_err);
if (!passwd)
- errx(exit_code, "%s", err);
+ errx(EXITCODE_ERROR_HSM_FILE, "Could not read password: %s", hsm_secret_error_str(pass_err));
+
printf("Confirm hsm_secret password:\n");
fflush(stdout);
- passwd_confirmation = read_stdin_pass_with_exit_code(&err, &exit_code);
+ passwd_confirmation = read_stdin_pass(tmpctx, &pass_err);
if (!passwd_confirmation)
- errx(exit_code, "%s", err);
+ errx(EXITCODE_ERROR_HSM_FILE, "Could not read password: %s", hsm_secret_error_str(pass_err));
+
if (!streq(passwd, passwd_confirmation))
errx(ERROR_USAGE, "Passwords confirmation mismatch.");
- get_unencrypted_hsm_secret(&hsm_secret, hsm_secret_path);
dir = path_dirname(NULL, hsm_secret_path);
backup = path_join(dir, dir, "hsm_secret.backup");
- /* Derive the encryption key from the password provided, and try to encrypt
- * the seed. */
- exit_code = hsm_secret_encryption_key_with_exitcode(passwd, &key, &err);
- if (exit_code > 0)
- errx(exit_code, "%s", err);
- if (!encrypt_hsm_secret(&key, &hsm_secret, &encrypted_hsm_secret))
+ /* Create encryption key and encrypt */
+ struct secret *encryption_key = get_encryption_key(tmpctx, passwd);
+ if (!encryption_key)
+ errx(ERROR_LIBSODIUM, "Could not derive encryption key");
+
+ if (!encrypt_legacy_hsm_secret(encryption_key, &hsms->secret, encrypted_hsm_secret))
errx(ERROR_LIBSODIUM, "Could not encrypt the hsm_secret seed.");
- /* Once the encryption key derived, we don't need it anymore. */
- free(passwd);
- free(passwd_confirmation);
+ /* Securely discard the encryption key */
+ destroy_secret(encryption_key);
/* Create a backup file, "just in case". */
rename(hsm_secret_path, backup);
fd = open(hsm_secret_path, O_CREAT|O_EXCL|O_WRONLY, 0400);
if (fd < 0)
- errx(EXITCODE_ERROR_HSM_FILE, "Could not open new hsm_secret");
+ err(EXITCODE_ERROR_HSM_FILE, "Could not open new hsm_secret");
/* Write the encrypted hsm_secret. */
- if (!write_all(fd, encrypted_hsm_secret.data,
- sizeof(encrypted_hsm_secret.data))) {
+ if (!write_all(fd, encrypted_hsm_secret,
+ ENCRYPTED_HSM_SECRET_LEN)) {
unlink_noerr(hsm_secret_path);
close(fd);
rename(backup, hsm_secret_path);
- errx(EXITCODE_ERROR_HSM_FILE, "Failure writing cipher to hsm_secret.");
+ err(EXITCODE_ERROR_HSM_FILE, "Failure writing cipher to hsm_secret.");
}
/* Be as paranoïd as in hsmd with the file state on disk. */
@@ -359,17 +243,71 @@ static int encrypt_hsm(const char *hsm_secret_path)
printf("Successfully encrypted hsm_secret. You'll now have to pass the "
"--encrypted-hsm startup option.\n");
- return 0;
}
-static int dump_commitments_infos(struct node_id *node_id, u64 channel_id,
- u64 depth, char *hsm_secret_path)
+/* Taken from hsmd. */
+static void get_channel_seed(struct secret *channel_seed, const struct node_id *peer_id,
+ u64 dbid, struct secret *hsm_secret)
+{
+ struct secret channel_base;
+ u8 input[sizeof(peer_id->k) + sizeof(dbid)];
+ const char *info = "per-peer seed";
+
+ hkdf_sha256(&channel_base, sizeof(struct secret), NULL, 0,
+ hsm_secret, sizeof(*hsm_secret),
+ "peer seed", strlen("peer seed"));
+ memcpy(input, peer_id->k, sizeof(peer_id->k));
+ BUILD_ASSERT(sizeof(peer_id->k) == PUBKEY_CMPR_LEN);
+ memcpy(input + PUBKEY_CMPR_LEN, &dbid, sizeof(dbid));
+
+ hkdf_sha256(channel_seed, sizeof(*channel_seed),
+ input, sizeof(input),
+ &channel_base, sizeof(channel_base),
+ info, strlen(info));
+}
+
+static void print_codexsecret(const char *hsm_secret_path, const char *id)
+{
+ struct secret hsm_secret;
+ char *bip93;
+ const char *err;
+ struct hsm_secret *hsms = load_hsm_secret(tmpctx, hsm_secret_path);
+ hsm_secret = hsms->secret;
+
+ err = codex32_secret_encode(tmpctx, "cl", id, 0, hsm_secret.data, 32, &bip93);
+ if (err)
+ errx(ERROR_USAGE, "%s", err);
+
+ printf("%s\n", bip93);
+}
+
+static void print_emergencyrecover(const char *emer_rec_path)
+{
+ size_t scb_len;
+ u8 *scb = grab_file_contents(tmpctx, emer_rec_path, &scb_len);
+ char *output, *hrp = "clnemerg";
+ if (!scb) {
+ err(EXITCODE_ERROR_HSM_FILE, "Reading emergency.recover");
+ }
+ u5 *data = tal_arr(tmpctx, u5, 0);
+
+ bech32_push_bits(&data, scb, scb_len * 8);
+ output = tal_arr(tmpctx, char, strlen(hrp) + tal_count(data) + 8);
+
+ bech32_encode(output, hrp, data, tal_count(data), (size_t)-1,
+ BECH32_ENCODING_BECH32);
+
+ printf("%s\n", output);
+}
+
+static void dump_commitments_infos(struct node_id *node_id, u64 channel_id,
+ u64 depth, char *hsm_secret_path)
{
struct sha256 shaseed;
struct secret hsm_secret, channel_seed, per_commitment_secret;
struct pubkey per_commitment_point;
-
- get_hsm_secret(&hsm_secret, hsm_secret_path);
+ struct hsm_secret *hsms = load_hsm_secret(tmpctx, hsm_secret_path);
+ hsm_secret = hsms->secret;
get_channel_seed(&channel_seed, node_id, channel_id, &hsm_secret);
derive_shaseed(&channel_seed, &shaseed);
@@ -385,35 +323,15 @@ static int dump_commitments_infos(struct node_id *node_id, u64 channel_id,
printf("commit point #%"PRIu64": %s\n",
i, fmt_pubkey(tmpctx, &per_commitment_point));
}
-
- return 0;
}
-/* In case of an unilateral close from the remote side while we suffered a
- * loss of data, this tries to recover the private key from the `to_remote`
- * output.
- * This basically iterates over every `dbid` to derive the channel_seed and
- * then derives the payment basepoint to compare to the pubkey hash specified
- * in the witness programm.
- * Note that since a node generates the key for the to_remote output from its
- * *local* per_commitment_point, there is nothing we can do if
- * `option_static_remotekey` was not negotiated.
- *
- * :param address: The bech32 address of the v0 P2WPKH witness programm
- * :param node_id: The id of the node with which the channel was established
- * :param tries: How many dbids to try.
- * :param hsm_secret_path: The path to the hsm_secret
- * :param passwd: The *optional* hsm_secret password
- */
-static int guess_to_remote(const char *address, struct node_id *node_id,
- u64 tries, char *hsm_secret_path)
+static void guess_to_remote(const char *address, struct node_id *node_id,
+ u64 tries, char *hsm_secret_path)
{
struct secret hsm_secret, channel_seed, basepoint_secret;
struct pubkey basepoint;
struct ripemd160 pubkeyhash;
- /* We only support P2WPKH, hence 20. */
u8 goal_pubkeyhash[20];
- /* See common/bech32.h for buffer size. */
char hrp[strlen(address) - 6];
int witver;
size_t witlen;
@@ -424,7 +342,8 @@ static int guess_to_remote(const char *address, struct node_id *node_id,
if (segwit_addr_decode(&witver, goal_pubkeyhash, &witlen, hrp, address) != 1)
errx(ERROR_USAGE, "Wrong bech32 address");
- get_hsm_secret(&hsm_secret, hsm_secret_path);
+ struct hsm_secret *hsms = load_hsm_secret(tmpctx, hsm_secret_path);
+ hsm_secret = hsms->secret;
for (u64 dbid = 1; dbid < tries ; dbid++) {
get_channel_seed(&channel_seed, node_id, dbid, &hsm_secret);
@@ -438,188 +357,103 @@ static int guess_to_remote(const char *address, struct node_id *node_id,
if (memcmp(pubkeyhash.u.u8, goal_pubkeyhash, 20) == 0) {
printf("bech32 : %s\n", address);
printf("pubkey hash : %s\n",
- tal_hexstr(tmpctx, pubkeyhash.u.u8, 20));
+ tal_hexstr(tmpctx, pubkeyhash.u.u8, 20));
printf("pubkey : %s \n",
- fmt_pubkey(tmpctx, &basepoint));
+ fmt_pubkey(tmpctx, &basepoint));
printf("privkey : %s \n",
- fmt_secret(tmpctx, &basepoint_secret));
- return 0;
+ fmt_secret(tmpctx, &basepoint_secret));
+ return;
}
}
- printf("Could not find any basepoint matching the provided witness programm.\n"
- "Are you sure that the channel used `option_static_remotekey` ?\n");
- return 1;
-}
-
-static int derive_to_remote(const struct unilateral_close_info *info, const char *hsm_secret_path)
-{
- struct secret hsm_secret, channel_seed, basepoint_secret;
- struct pubkey basepoint;
- struct privkey privkey;
-
- secp256k1_ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY
- | SECP256K1_CONTEXT_SIGN);
-
- get_hsm_secret(&hsm_secret, hsm_secret_path);
- get_channel_seed(&channel_seed, &info->peer_id, info->channel_id, &hsm_secret);
- if (!derive_payment_basepoint(&channel_seed, &basepoint, &basepoint_secret))
- errx(ERROR_KEYDERIV, "Could not derive basepoints for dbid %"PRIu64
- " and channel seed %s.", info->channel_id,
- fmt_secret(tmpctx, &channel_seed));
- if (!info->commitment_point)
- privkey.secret = basepoint_secret;
- else if (!derive_simple_privkey(&basepoint_secret, &basepoint, info->commitment_point, &privkey))
- errx(ERROR_KEYDERIV, "Could not derive simple privkey for dbid %"PRIu64
- ", channel seed %s, and commitment point %s.", info->channel_id,
- fmt_secret(tmpctx, &channel_seed),
- fmt_pubkey(tmpctx, info->commitment_point));
- printf("privkey : %s\n", fmt_secret(tmpctx, &privkey.secret));
- return 0;
+ errx(ERROR_USAGE, "Could not find any basepoint matching the provided witness programm.\n"
+ "Are you sure that the channel used `option_static_remotekey` ?");
}
-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)
+static void generate_hsm(const char *hsm_secret_path)
{
- 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) {
-
- 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)-1);
- fflush(stdout);
-
- 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].");
-
- free(selected);
- bip39_get_wordlist(languages[val].abbr, words);
-}
+ const char *mnemonic, *passphrase;
+ enum hsm_secret_error error;
-static char *get_mnemonic(void) {
- char *line = NULL;
- size_t line_size = 0;
+ /* Get mnemonic from user using consistent interface */
+ mnemonic = read_stdin_mnemonic(tmpctx, &error);
+ if (!mnemonic)
+ errx(EXITCODE_ERROR_HSM_FILE, "Could not read mnemonic: %s", hsm_secret_error_str(error));
- printf("Introduce your BIP39 word list separated by space (at least 12 words):\n");
+ /* Get optional passphrase */
+ 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);
- size_t characters = getline(&line, &line_size, stdin);
- if (characters < 0)
- errx(ERROR_USAGE, "Could not read line from stdin.");
- line[characters-1] = '\0';
- return line;
-}
-
-static char *read_mnemonic(void) {
- /* Get words for the mnemonic language */
- struct words *words;
- get_words(&words);
-
- /* Get mnemonic */
- char *mnemonic;
- mnemonic = get_mnemonic();
-
- if (bip39_mnemonic_validate(words, mnemonic) != 0) {
- errx(ERROR_USAGE, "Invalid mnemonic: \"%s\"", mnemonic);
+ passphrase = read_stdin_pass(tmpctx, &error);
+ if (!passphrase)
+ errx(EXITCODE_ERROR_HSM_FILE, "Could not read passphrase: %s", hsm_secret_error_str(error));
+ if (streq(passphrase, "")) {
+ passphrase = NULL;
}
- return mnemonic;
-}
-
-static int generate_hsm(const char *hsm_secret_path,
- const char *lang_id,
- char *mnemonic,
- char *passphrase)
-{
- const char *err;
- int exit_code = 0;
-
- 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);
+ /* Write to file using your new mnemonic format */
+ int fd = open(hsm_secret_path, O_CREAT|O_EXCL|O_WRONLY, 0400);
+ if (fd < 0) {
+ err(ERROR_USAGE, "Unable to create hsm_secret file");
}
- u8 bip32_seed[BIP39_SEED_LEN_512];
- size_t bip32_seed_len;
+ /* Hash the derived seed for validation */
+ struct sha256 seed_hash;
+ if (!derive_seed_hash(mnemonic, passphrase, &seed_hash))
+ errx(ERROR_USAGE, "Error deriving seed from 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");
+ /* Write seed hash (32 bytes) + mnemonic */
+ if (!write_all(fd, &seed_hash, sizeof(seed_hash)))
+ err(ERROR_USAGE, "Error writing seed hash to hsm_secret file");
- 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");
- }
- /* Write only the first 32 bytes, length of the (plaintext) seed in the
- * hsm_secret. */
- if (!write_all(fd, bip32_seed, 32))
- errx(ERROR_USAGE, "Error writing secret to hsm_secret file");
+ /* Write the mnemonic */
+ if (!write_all(fd, mnemonic, strlen(mnemonic)))
+ err(ERROR_USAGE, "Error writing mnemonic to hsm_secret file");
if (fsync(fd) != 0)
- errx(ERROR_USAGE, "Error fsyncing hsm_secret file");
+ err(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");
+ err(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");
+ printf("Format: %s\n", passphrase ? "mnemonic with passphrase" : "mnemonic without passphrase");
+ if (passphrase) {
+ printf("Remember your passphrase - it's required to use this hsm_secret!\n");
+ }
- free(mnemonic);
- free(passphrase);
- return 0;
+ /* passphrase and mnemonic will be automatically cleaned up by tmpctx */
}
-static int dumponchaindescriptors(const char *hsm_secret_path,
- const char *old_passwd UNUSED,
- const u32 version, bool show_secrets)
+static void derive_to_remote(const struct unilateral_close_info *info, const char *hsm_secret_path)
+{
+ struct secret hsm_secret, channel_seed, basepoint_secret;
+ struct pubkey basepoint;
+ struct privkey privkey;
+
+ secp256k1_ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY
+ | SECP256K1_CONTEXT_SIGN);
+
+ struct hsm_secret *hsms = load_hsm_secret(tmpctx, hsm_secret_path);
+ hsm_secret = hsms->secret;
+ get_channel_seed(&channel_seed, &info->peer_id, info->channel_id, &hsm_secret);
+ if (!derive_payment_basepoint(&channel_seed, &basepoint, &basepoint_secret))
+ errx(ERROR_KEYDERIV, "Could not derive basepoints for dbid %"PRIu64
+ " and channel seed %s.", info->channel_id,
+ fmt_secret(tmpctx, &channel_seed));
+ if (!info->commitment_point)
+ privkey.secret = basepoint_secret;
+ else if (!derive_simple_privkey(&basepoint_secret, &basepoint, info->commitment_point, &privkey))
+ errx(ERROR_KEYDERIV, "Could not derive simple privkey for dbid %"PRIu64
+ ", channel seed %s, and commitment point %s.", info->channel_id,
+ fmt_secret(tmpctx, &channel_seed),
+ fmt_pubkey(tmpctx, info->commitment_point));
+ printf("privkey : %s\n", fmt_secret(tmpctx, &privkey.secret));
+}
+static void dumponchaindescriptors(const char *hsm_secret_path,
+ const u32 version, bool show_secrets)
{
struct secret hsm_secret;
u8 bip32_seed[BIP32_ENTROPY_LEN_256];
@@ -627,10 +461,8 @@ static int dumponchaindescriptors(const char *hsm_secret_path,
struct ext_key master_extkey;
char *enc_xkey, *descriptor;
struct descriptor_checksum checksum;
-
- get_hsm_secret(&hsm_secret, hsm_secret_path);
-
- /* We use m/0/0/k as the derivation tree for onchain funds. */
+ struct hsm_secret *hsms = load_hsm_secret(tmpctx, hsm_secret_path);
+ hsm_secret = hsms->secret;
/* The root seed is derived from hsm_secret using hkdf.. */
do {
@@ -675,57 +507,59 @@ static int dumponchaindescriptors(const char *hsm_secret_path,
tal_free(descriptor);
wally_free_string(enc_xkey);
-
- return 0;
}
-static int check_hsm(const char *hsm_secret_path)
+/* Check HSM secret by comparing with backup mnemonic */
+static void check_hsm(const char *hsm_secret_path)
{
- char *mnemonic;
- struct secret hsm_secret;
+ struct secret file_secret, derived_secret;
u8 bip32_seed[BIP39_SEED_LEN_512];
size_t bip32_seed_len;
- int exit_code;
- char *passphrase;
- const char *err;
+ const char *mnemonic_passphrase, *mnemonic;
+ enum hsm_secret_error err;
- get_hsm_secret(&hsm_secret, hsm_secret_path);
+ /* Load the hsm_secret (handles decryption automatically if needed) */
+ struct hsm_secret *hsms = load_hsm_secret(tmpctx, hsm_secret_path);
+ file_secret = hsms->secret;
+ /* Ask user for their backup mnemonic passphrase */
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");
+ printf("Enter your mnemonic 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;
+ mnemonic_passphrase = read_stdin_pass(tmpctx, &err);
+ if (!mnemonic_passphrase)
+ errx(EXITCODE_ERROR_HSM_FILE, "Could not read passphrase: %s", hsm_secret_error_str(err));
+ if (streq(mnemonic_passphrase, "")) {
+ mnemonic_passphrase = NULL;
}
- mnemonic = read_mnemonic();
- if (bip39_mnemonic_to_seed(mnemonic, passphrase, bip32_seed, sizeof(bip32_seed), &bip32_seed_len) != WALLY_OK)
+ /* Ask user for their backup mnemonic using consistent interface */
+ mnemonic = read_stdin_mnemonic(tmpctx, &err);
+ if (!mnemonic)
+ errx(EXITCODE_ERROR_HSM_FILE, "Could not read mnemonic: %s", hsm_secret_error_str(err));
+
+ /* Derive seed from user's backup mnemonic + passphrase */
+ if (bip39_mnemonic_to_seed(mnemonic, mnemonic_passphrase, bip32_seed, sizeof(bip32_seed), &bip32_seed_len) != WALLY_OK)
errx(ERROR_LIBWALLY, "Unable to derive BIP32 seed from BIP39 mnemonic");
- /* We only use first 32 bytes */
- if (memcmp(bip32_seed, hsm_secret.data, sizeof(hsm_secret.data)) != 0)
+ /* Copy first 32 bytes to our secret for comparison */
+ memcpy(derived_secret.data, bip32_seed, sizeof(derived_secret.data));
+
+ /* Compare the seeds */
+ if (memcmp(derived_secret.data, file_secret.data, sizeof(file_secret.data)) != 0)
errx(ERROR_KEYDERIV, "resulting hsm_secret did not match");
printf("OK\n");
-
- free(mnemonic);
- free(passphrase);
- return 0;
}
-static int make_rune(const char *hsm_secret_path)
+static void make_rune(const char *hsm_secret_path)
{
struct secret hsm_secret, derived_secret, rune_secret;
struct rune *master_rune, *rune;
-
- /* Get hsm_secret */
- get_hsm_secret(&hsm_secret, hsm_secret_path);
+ struct hsm_secret *hsms = load_hsm_secret(tmpctx, hsm_secret_path);
+ hsm_secret = hsms->secret;
/* HSM derives a root secret for `makesecret` */
hkdf_sha256(&derived_secret, sizeof(struct secret), NULL, 0,
@@ -743,18 +577,16 @@ static int make_rune(const char *hsm_secret_path)
NULL);
rune = rune_derive_start(tmpctx, master_rune, "0");
printf("%s\n", rune_to_base64(tmpctx, rune));
- return 0;
}
-static int get_node_id(const char *hsm_secret_path)
+static void print_node_id(const char *hsm_secret_path)
{
u32 salt = 0;
struct secret hsm_secret;
struct privkey node_privkey;
struct pubkey node_id;
-
- /* Get hsm_secret */
- get_hsm_secret(&hsm_secret, hsm_secret_path);
+ struct hsm_secret *hsms = load_hsm_secret(tmpctx, hsm_secret_path);
+ hsm_secret = hsms->secret;
/*~ So, there is apparently a 1 in 2^127 chance that a random value is
* not a valid private key, so this never actually loops. */
@@ -772,7 +604,6 @@ static int get_node_id(const char *hsm_secret_path)
node_privkey.secret.data));
printf("%s\n", fmt_pubkey(tmpctx, &node_id));
- return 0;
}
int main(int argc, char *argv[])
@@ -791,38 +622,33 @@ int main(int argc, char *argv[])
if (streq(method, "decrypt")) {
if (argc < 3)
show_usage(argv[0]);
- return decrypt_hsm(argv[2]);
- }
-
- if (streq(method, "encrypt")) {
+ decrypt_hsm(argv[2]);
+ } else if (streq(method, "encrypt")) {
if (argc < 3)
show_usage(argv[0]);
- return encrypt_hsm(argv[2]);
- }
+ encrypt_hsm(argv[2]);
- if (streq(method, "dumpcommitments")) {
+ } else if (streq(method, "dumpcommitments")) {
/* node_id channel_id depth hsm_secret */
if (argc < 6)
show_usage(argv[0]);
struct node_id node_id;
if (!node_id_from_hexstr(argv[2], strlen(argv[2]), &node_id))
errx(ERROR_USAGE, "Bad node id");
- return dump_commitments_infos(&node_id, atol(argv[3]), atol(argv[4]),
- argv[5]);
- }
+ dump_commitments_infos(&node_id, atol(argv[3]), atol(argv[4]),
+ argv[5]);
- if (streq(method, "guesstoremote")) {
+ } else if (streq(method, "guesstoremote")) {
/* address node_id depth hsm_secret */
if (argc < 6)
show_usage(argv[0]);
struct node_id node_id;
if (!node_id_from_hexstr(argv[3], strlen(argv[3]), &node_id))
errx(ERROR_USAGE, "Bad node id");
- return guess_to_remote(argv[2], &node_id, atol(argv[4]),
- argv[5]);
- }
+ guess_to_remote(argv[2], &node_id, atol(argv[4]),
+ argv[5]);
- if (streq(method, "derivetoremote")) {
+ } else if (streq(method, "derivetoremote")) {
/* node_id channel_id [commitment_point] hsm_secret */
if (argc < 5 || argc > 6)
show_usage(argv[0]);
@@ -836,16 +662,13 @@ int main(int argc, char *argv[])
errx(ERROR_USAGE, "Bad commitment point");
info.commitment_point = &commitment_point;
}
- return derive_to_remote(&info, argv[argc - 1]);
- }
+ derive_to_remote(&info, argv[argc - 1]);
- if (streq(method, "generatehsm")) {
- // argv[2] file, argv[3] lang_id, argv[4] word list, argv[5] passphrase
- if (argc < 3 || argc > 6 || argc == 4)
+ } else if (streq(method, "generatehsm")) {
+ if (argc != 3)
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 */
@@ -853,18 +676,8 @@ 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);
- lang_id = (argc > 3 ? argv[3] : NULL);
- if (lang_id && !check_lang(lang_id))
- show_usage(argv[0]);
-
- /* generate_hsm expects to free these, so use strdup */
- word_list = (argc > 4 ? strdup(argv[4]) : NULL);
- passphrase = (argc > 5 ? strdup(argv[5]) : NULL);
-
- return generate_hsm(hsm_secret_path, lang_id, word_list, passphrase);
- }
-
- if (streq(method, "dumponchaindescriptors")) {
+ generate_hsm(hsm_secret_path);
+ } else if (streq(method, "dumponchaindescriptors")) {
char *fname = NULL;
char *net = NULL;
bool show_secrets = false;
@@ -914,38 +727,30 @@ int main(int argc, char *argv[])
else
version = BIP32_VER_MAIN_PRIVATE;
- return dumponchaindescriptors(fname, NULL, version, show_secrets);
- }
-
- if (streq(method, "checkhsm")) {
+ dumponchaindescriptors(fname, version, show_secrets);
+ } else if (streq(method, "checkhsm")) {
if (argc < 3)
show_usage(argv[0]);
- return check_hsm(argv[2]);
- }
-
- if (streq(method, "makerune")) {
+ check_hsm(argv[2]);
+ } else if (streq(method, "makerune")) {
if (argc < 3)
show_usage(argv[0]);
- return make_rune(argv[2]);
- }
-
- if(streq(method, "getcodexsecret")) {
+ make_rune(argv[2]);
+ } else if(streq(method, "getcodexsecret")) {
if (argc < 4)
show_usage(argv[0]);
- return make_codexsecret(argv[2], argv[3]);
- }
-
- if(streq(method, "getemergencyrecover")) {
+ print_codexsecret(argv[2], argv[3]);
+ } else if(streq(method, "getemergencyrecover")) {
if (argc < 3)
show_usage(argv[0]);
- return getemergencyrecover(argv[2]);
- }
-
- if (streq(method, "getnodeid")) {
+ print_emergencyrecover(argv[2]);
+ } else if (streq(method, "getnodeid")) {
if (argc < 3)
show_usage(argv[0]);
- return get_node_id(argv[2]);
+ print_node_id(argv[2]);
+ } else {
+ show_usage(argv[0]);
}
- show_usage(argv[0]);
+ return 0;
}
Why this scored 32/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.