Skip to content

Commit a623954

Browse files
author
Ambrogio
committed
Harden self-test and optional QR packaging
1 parent cc15fac commit a623954

7 files changed

Lines changed: 31 additions & 13 deletions

File tree

‎README.md‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ The original webcam/audio/mouse implementation is preserved unchanged under `leg
1212

1313
## Release Status
1414

15-
Current project version: **2.3.0**
15+
Current project version: **2.3.1**
1616

1717
The maintained CLI is tested on Windows and Linux with Python 3.11, 3.12, and 3.13 through GitHub Actions.
1818

@@ -25,7 +25,6 @@ Python 3.11 or newer is recommended.
2525
Install the CLI from a local checkout:
2626

2727
```bash
28-
python -m pip install -r requirements.txt
2928
python -m pip install .
3029
entropyseed --self-test
3130
```
@@ -42,19 +41,25 @@ The base install has no runtime dependency beyond Python's standard library. Ins
4241
python -m pip install '.[qr]'
4342
```
4443

44+
For development and test runs, install the test dependencies:
45+
46+
```bash
47+
python -m pip install -r requirements.txt
48+
python -m pytest -q
49+
```
50+
4551
---
4652

4753
## Running From The Repository
4854

4955
You can also run the repository entry point directly:
5056

5157
```bash
52-
python -m pip install -r requirements.txt
5358
python seedgen.py --self-test
5459
python seedgen.py
5560
```
5661

57-
The generator uses the Python standard library for normal mnemonic generation. `qrcode` is used only for optional terminal QR output, and `pytest` is listed for the test suite.
62+
The generator uses the Python standard library for normal mnemonic generation. `pytest` is listed only for development and test runs. `qrcode` is installed only through the optional `.[qr]` extra when terminal QR output is wanted.
5863

5964
---
6065

@@ -128,14 +133,16 @@ If `--qr` is requested without QR support installed, the program exits before co
128133

129134
## Entropy Sources
130135

131-
OS CSPRNG entropy from `secrets.token_bytes` is always included and cannot be disabled. Manual typing, dice rolls, and timer jitter are supplemental only — they strengthen the pool, they do not replace it.
136+
OS CSPRNG entropy from `secrets.token_bytes` is the mandatory foundation and cannot be disabled. Dice rolls are a strong independent supplement when the dice are fair, private, and used correctly. Manual typing is an optional supplement, but the tool does not assume or quantify its entropy. Timer jitter is an opportunistic local supplement, not a substitute for the OS CSPRNG.
132137

133138
Generation fails if the mandatory OS CSPRNG source is missing or returns an unexpected amount of entropy. The tool is designed to fail closed rather than silently downgrade to weaker supplemental sources.
134139

135140
Dice input accepts digits `1` through `6`; spaces are ignored. For 12-word generation, at least 50 dice rolls are required. For 24-word generation, at least 99 dice rolls are required.
136141

137142
Timer jitter displays a short collection message and progress dots. It is collected in memory and should complete quickly.
138143

144+
The supported CLI path is `collect_sources() -> derive_mnemonic()`. Internally, `derive_mnemonic()` requires the mandatory OS source label (`os-csprng`) before mixing; this is a fail-closed software check for the supported path, not cryptographic proof of byte provenance.
145+
139146
---
140147

141148
## Security Notes

‎entropyseed/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
__all__ = ["__version__"]
44

5-
__version__ = "2.3.0"
5+
__version__ = "2.3.1"

‎entropyseed/cli.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ def collect_sources(args: argparse.Namespace) -> list[EntropySource]:
7777

7878

7979
def derive_mnemonic(sources: list[EntropySource], strength: int) -> str:
80+
# Supported CLI path: collect_sources() labels the mandatory OS CSPRNG source.
81+
# This fail-closed check is not cryptographic proof of byte provenance.
8082
if not any(source.name == MANDATORY_OS_SOURCE for source in sources):
8183
raise ValueError("mandatory OS CSPRNG entropy source is missing")
8284

‎entropyseed/selftest.py‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ def run_self_tests() -> list[SelfTestResult]:
2222
_check_bip39_vectors(),
2323
_check_hkdf_vectors(),
2424
_check_entropy_mixer_os_source(),
25-
_check_no_file_output_required(),
2625
]
2726

2827

@@ -71,7 +70,3 @@ def _check_entropy_mixer_os_source() -> SelfTestResult:
7170
except Exception as exc:
7271
return SelfTestResult("entropy mixer OS source", False, str(exc))
7372

74-
75-
def _check_no_file_output_required() -> SelfTestResult:
76-
return SelfTestResult("no file output required", True)
77-

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "entropyseed"
7-
version = "2.3.0"
7+
version = "2.3.1"
88
description = "Offline BIP39 mnemonic generator with mandatory OS CSPRNG entropy."
99
readme = "README.md"
1010
requires-python = ">=3.11"

‎requirements.txt‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
pytest>=8.0
2-
qrcode>=8.0

‎tests/test_cli.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import subprocess
22
import sys
33
import types
4+
from pathlib import Path
45

56
from entropyseed.cli import (
67
build_parser,
@@ -48,6 +49,19 @@ def fail_print_mnemonic_qr(mnemonic):
4849
assert main([]) == 0
4950

5051

52+
def test_main_without_arguments_does_not_create_files(monkeypatch, tmp_path, capsys):
53+
monkeypatch.chdir(tmp_path)
54+
monkeypatch.setattr("entropyseed.cli.word_count_for_strength", lambda strength: 3)
55+
monkeypatch.setattr("entropyseed.cli.collect_sources", lambda args: ["controlled-source"])
56+
monkeypatch.setattr("entropyseed.cli.derive_mnemonic", lambda sources, strength: "alpha beta gamma")
57+
58+
assert main([]) == 0
59+
60+
captured = capsys.readouterr()
61+
assert "alpha beta gamma" in captured.out
62+
assert list(Path(tmp_path).iterdir()) == []
63+
64+
5165
def test_parser_accepts_qr():
5266
args = build_parser().parse_args(["--qr"])
5367
assert args.qr is True
@@ -190,6 +204,7 @@ def print_ascii(self, out):
190204
def test_main_prints_qr_when_requested(monkeypatch, capsys):
191205
calls = {}
192206

207+
monkeypatch.setattr("entropyseed.cli.qr_support_available", lambda: True)
193208
monkeypatch.setattr("entropyseed.cli.word_count_for_strength", lambda strength: 3)
194209
monkeypatch.setattr("entropyseed.cli.collect_sources", lambda args: ["source"])
195210
monkeypatch.setattr("entropyseed.cli.derive_mnemonic", lambda sources, strength: "alpha beta gamma")

0 commit comments

Comments
 (0)