pyln-testing: add old_hsmsecret param to get_node.
What changed, and why it matters
This commit is a testing-only change. It adds an option to the Core Lightning Python test framework so that tests can choose whether to create nodes using the older style 'hsm_secret' file or the newer BIP39-style mnemonic secret. It does not change production code, does not fix a vulnerability, and does not introduce an obvious security bug.
No security action required. This is a test-framework compatibility change. Reviewers may want to verify that the new mnemonic generation path does not accidentally weaken test determinism or leak entropy, but the change is confined to the testing harness.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch modifies contrib/pyln-testing/pyln/testing/utils.py to add an ‘old_hsmsecret’ parameter to LightningD, LightningNode, and NodeFactory. When old_hsmsecret is False, the test harness generates a 12-word BIP39 mnemonic from 128 bits of entropy and writes the hsm_secret as 32 zero bytes followed by the mnemonic bytes. When True (the current default), it preserves the previous behavior of writing a 32-byte seed derived from the lightning_dir path. The commit also adds the ‘mnemonic’ Python package as a dependency in pyproject.toml and uv.lock. There is no production node code change.
Changed components
contrib/pyln-testing/pyln/testing/utils.pypyproject.tomluv.lockInspect captured patch +43 / −2
diff --git a/contrib/pyln-testing/pyln/testing/utils.py b/contrib/pyln-testing/pyln/testing/utils.py
index 732bd7b4..2159ef87 100644
--- a/contrib/pyln-testing/pyln/testing/utils.py
+++ b/contrib/pyln-testing/pyln/testing/utils.py
@@ -17,6 +17,7 @@ import json
import logging
import lzma
import math
+import mnemonic
import os
import random
import re
@@ -664,6 +665,14 @@ class ElementsD(BitcoinD):
return info['unconfidential']
+def mnemonic_from_seed(seed):
+ m = mnemonic.Mnemonic('english')
+ mnem = m.to_mnemonic(seed)
+ if not m.check(mnem):
+ raise RuntimeError("Generated mnemonic failed BIP39 validation (unexpected).")
+ return mnem
+
+
class LightningD(TailableProc):
def __init__(
self,
@@ -673,6 +682,7 @@ class LightningD(TailableProc):
random_hsm=False,
node_id=0,
executable=None,
+ old_hsmsecret=None,
):
# We handle our own version of verbose, below.
TailableProc.__init__(self, lightning_dir, verbose=False)
@@ -714,11 +724,27 @@ class LightningD(TailableProc):
if not os.path.exists(os.path.join(lightning_dir, TEST_NETWORK)):
os.makedirs(os.path.join(lightning_dir, TEST_NETWORK))
- # Last 32-bytes of final part of dir -> seed.
- seed = (bytes(re.search('([^/]+)/*$', lightning_dir).group(1), encoding='utf-8') + bytes(32))[:32]
+ # Default: use old-timey hsm_secret.
+ if old_hsmsecret is None:
+ old_hsmsecret = True
+
+ # BIP 39 secrets were only added in v25.12.
+ if old_hsmsecret is True:
+ assert self.cln_version >= "v25.12"
+
if not random_hsm:
+ # Last 32-bytes of final part of dir -> seed.
+ seed = (bytes(re.search('([^/]+)/*$', lightning_dir).group(1), encoding='utf-8') + bytes(32))[:32]
+ # Modern style is 32 zeroes then a 12-word mnemonic phrase.
+ if not old_hsmsecret:
+ # Use first 16 bytes (128 bits) for 12-word mnemonic
+ entropy_128 = seed[:16]
+ mnemonic_phrase = mnemonic_from_seed(entropy_128)
+ seed = bytes(32) + bytes(mnemonic_phrase, encoding='utf-8')
+
with open(os.path.join(lightning_dir, TEST_NETWORK, 'hsm_secret'), 'wb') as f:
f.write(seed)
+
self.opts['dev-fast-gossip'] = None
self.opts['dev-bitcoind-poll'] = 1
self.prefix = 'lightningd-%d' % (node_id)
@@ -851,6 +877,7 @@ class LightningNode(object):
valgrind_plugins=True,
executable=None,
bad_notifications=False,
+ old_hsmsecret=None,
**kwargs):
self.bitcoin = bitcoind
self.executor = executor
@@ -875,6 +902,7 @@ class LightningNode(object):
lightning_dir, bitcoindproxy=bitcoind.get_proxy(),
port=port, random_hsm=random_hsm, node_id=node_id,
executable=executable,
+ old_hsmsecret=old_hsmsecret,
)
self.cln_version = self.daemon.cln_version
@@ -1689,6 +1717,7 @@ class NodeFactory(object):
'allow_bad_gossip',
'start',
'gossip_store_file',
+ 'old_hsmsecret',
]
node_opts = {k: v for k, v in opts.items() if k in node_opt_keys}
cli_opts = {k: v for k, v in opts.items() if k not in node_opt_keys}
diff --git a/pyproject.toml b/pyproject.toml
index b86ffef5..76335c2e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -19,6 +19,7 @@ dependencies = [
"pytest-rerunfailures>=16.0.1",
"pyln-testing",
"pyln-proto",
+ "mnemonic>=0.21",
]
package-mode = false
[dependency-groups]
diff --git a/uv.lock b/uv.lock
index 9373dedf..0c75e49f 100644
--- a/uv.lock
+++ b/uv.lock
@@ -460,6 +460,7 @@ dependencies = [
{ name = "grpcio" },
{ name = "grpcio-tools" },
{ name = "mako" },
+ { name = "mnemonic" },
{ name = "protobuf" },
{ name = "pyln-client" },
{ name = "pyln-grpc-proto" },
@@ -501,6 +502,7 @@ requires-dist = [
{ name = "grpcio", specifier = "==1.75.1" },
{ name = "grpcio-tools", specifier = "==1.75.1" },
{ name = "mako", specifier = ">=1.1.6" },
+ { name = "mnemonic", specifier = ">=0.21" },
{ name = "protobuf", specifier = "==6.32.1" },
{ name = "pyln-client", editable = "contrib/pyln-client" },
{ name = "pyln-grpc-proto", editable = "contrib/pyln-grpc-proto" },
@@ -1264,6 +1266,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" },
]
+[[package]]
+name = "mnemonic"
+version = "0.21"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ff/77/e6232ed59fbd7b90208bb8d4f89ed5aabcf30a524bc2fb8f0dafbe8e7df9/mnemonic-0.21.tar.gz", hash = "sha256:1fe496356820984f45559b1540c80ff10de448368929b9c60a2b55744cc88acf", size = 152462, upload-time = "2024-01-05T10:46:14.895Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/57/48/5abb16ce7f9d97b728e6b97c704ceaa614362e0847651f379ed0511942a0/mnemonic-0.21-py3-none-any.whl", hash = "sha256:72dc9de16ec5ef47287237b9b6943da11647a03fe7cf1f139fc3d7c4a7439288", size = 92701, upload-time = "2024-01-05T10:46:12.703Z" },
+]
+
[[package]]
name = "more-itertools"
version = "10.8.0"
Why this scored 14/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.