pip install --require-hashes in liboqs's Root Dependency Fileliboqs's README instructs new contributors to set up their Python tooling with:
pip install --require-hashes -r requirements.txt
This command failed on a clean checkout. Root cause: requirements.txt was missing a pinned, hashed entry for charset-normalizer, a transitive dependency of requests==2.34.2. A second, related gap existed: requirements.in — the source file the project's own docs describe as the input to requirements.txt — didn't exist in the repository at all.
python3 -m venv .venv
.venv/bin/pip install --require-hashes -r requirements.txt
Collecting charset_normalizer<4,>=2 (from requests==2.34.2->-r requirements.txt (line 116))
ERROR: In --require-hashes mode, all requirements must have their versions pinned with ==. These do not:
charset_normalizer<4,>=2 from ...
Every other package in the file installed cleanly, each pinned to an exact version with a --hash=sha256:... entry. charset_normalizer was the sole exception — never listed directly in requirements.txt, only pulled in implicitly through requests' own dependency metadata (charset_normalizer<4,>=2, an unpinned range).
--require-hashes mode rejects any resolved package that isn't explicitly pinned and hashed in the file being installed from, regardless of whether the transitive package itself resolves fine under normal pip. This is by design — the mode exists to guarantee every installed artifact, direct or transitive, is traceable to a reviewed, hash-verified entry.
The file's own header claimed:
# Generated by: pip-compile --generate-hashes requirements.in
requirements.in is meant to be the short, human-edited source file — direct dependencies only, no hashes — that pip-compile (from pip-tools) expands into the full pinned-and-hashed requirements.txt. Checking for it:
$ ls requirements.in
ls: cannot access 'requirements.in': No such file or directory
It didn't exist. The project's own docs/PROCEDURES.md documents this exact workflow — update requirements.in, regenerate requirements.txt via pip-compile --generate-hashes, check in both files — but the source file referenced by that workflow, and by the generated file's own header, was missing from the repository.
Direct dependencies were identified by checking which packages in requirements.txt had no # via <package> annotation — the convention pip-compile uses to mark a package as pulled in transitively by something else. Four packages had none: pytest, pytest-xdist, pyyaml, requests. These became requirements.in, pinned to their currently-installed versions to avoid an unrelated version bump across the rest of the file:
pytest==9.1.1
pytest-xdist==3.8.0
pyyaml==6.0.3
requests==2.34.2
pip-compile --generate-hashes --output-file=requirements.txt requirements.in
This resolved and added charset-normalizer==3.5.1 with real hashes. However, the recorded command in the output file's own header read:
pip-compile --generate-hashes --no-index --output-file=requirements.txt requirements.in
--no-index was never passed. Initial concern: had pip-compile silently resolved from a stale local cache instead of querying PyPI?
Investigation into pip-tools' own behavior clarified this: since every dependency in requirements.in is pinned to an exact version (no ranges), pip-compile has no need to query PyPI's general package index — which exists to answer "what versions satisfy this range." It still made a genuine network call to a separate endpoint, PyPI's JSON API, specifically to fetch the file hashes for each already-known version. --no-index in the header accurately reflects that the version-resolution index was unnecessary, not that no network request occurred.
This was independently confirmed: a completely separate resolution, run in an unrelated environment, produced an identical first hash for charset-normalizer==3.5.1:
--hash=sha256:00668ebb0609751758682eb0b5857e7c35b9f00e84dfdef062e103244ec94d45
Two independent SHA-256 computations matching character-for-character rules out a caching artifact.
A direct pip-compile regeneration also rewrote the file's original hand-written header (install instructions, a README pointer) with pip-compile's own generic boilerplate — an unrelated, unnecessary change. Fixed by regenerating with --no-header and restoring the original header manually, verified with:
git diff requirements.txt | grep "^-" | grep -v "^---"
Empty output confirmed a pure addition: one new dependency block, zero deletions.
Fresh virtual environment, fresh clone state, no cached artifacts:
rm -rf .venv
python3 -m venv .venv
.venv/bin/pip install --require-hashes -r requirements.txt
Successfully installed certifi-2025.6.15 charset-normalizer-3.5.1 execnet-2.1.2
idna-3.15 iniconfig-2.3.0 packaging-26.2 pluggy-1.6.0 pygments-2.20.0 pytest-9.1.1
pytest-xdist-3.8.0 pyyaml-6.0.3 requests-2.34.2 urllib3-2.7.0
Merged as PR #2578 into open-quantum-safe/liboqs:main, approved by two maintainers, 75/75 CI checks passed. Diff: two files, 184 insertions, 0 deletions — requirements.in added, requirements.txt extended with the missing pin.