Repository navigation
pq-verify v2.6.7
pq-verify v2.6.7
pq-verify's central claim is that self-testing cannot detect a consistent
error — that an implementation can be internally coherent and externally wrong,
and only an external reference finds the difference. This release applies that
standard to pq-verify itself.
A systematic sweep of every skip path found four checks that could report
success without executing, and adds a scheme-level audit for libraries whose
internals are not exported.
(2.6.6 was prepared but never published; its contents are included here.)
Self-audit: four checks that could pass without running
A check that reports success without executing is the exact failure mode this
tool exists to detect in other people's code. In CI a skip reads as green.
Every path where a check could be silently omitted was enumerated and
examined — not the specific instances already known, but the whole class.
| Check | Was | Now |
|---|---|---|
pq-verify --audit-so |
called pqverify_scan without engines, so Freivalds never ran — every CLI audit reported 2/2 |
engines compiled first; 3/3 |
| SLH-DSA live roundtrip | imported slh_dsa; the PyPI package slh-dsa installs as slhdsa, so this check had never run for anyone |
runs, plus a tampered-message negative control |
| CryptoMiniSat comparison | asserted True when CMS5 was absent — a benchmark that did not run counted toward 160/160 |
reports SKIPPED and does not count |
| FIPS 204 parameter validation | without dilithium-py, asserted the hardcoded constants against themselves |
reports SKIPPED — there is nothing to check them against |
| Coq certificate | 'certificate generated' asserted True before coqc was invoked |
checks the file exists; verification remains the separate coqc test |
No hardcoded-True assertions remain in any skip path.
Degradation is now visible
Every missing engine, dependency and skipped check is recorded and reported at
the end of every run:
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
DEGRADED RUN — this run verified LESS than pq-verify claims:
- engines unavailable: gf2, zq, zq32 (install gcc/g++ — checks
depending on these did NOT run)
A pass here does NOT mean full verification.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
--require-full-coverage exits non-zero on a degraded run, so CI cannot go
green on reduced coverage. Coverage state is included in --json output.
The test count is now environment-honest: 159/160 without cryptominisat,
160/160 with it, and the tool names the difference. Installing it also
produces a live, reproducible comparison — currently 1231× against CMS5 on
the AES S-box, measured rather than remembered.
New: --audit-kem — scheme-level third-party audit
Production libraries hide their internal transforms but export the public KEM
API. BoringSSL declares its NTT inline inside an anonymous namespace; AWS-LC
compiles mlkem-native with MLK_CONFIG_INTERNAL_API_QUALIFIER=static; wolfSSL
marks it static. None can be symbol-audited at the NTT level — but all expose
keygen, encapsulate and decapsulate, which is what customers actually run.
pq-verify --audit-kem lib.so ML-KEM-768This drives the vendor's own code with NIST's ACVP vectors:
keygen : NIST (d,z) -> vendor ek,dk byte-exact
encaps : NIST (ek,m) -> vendor c,K byte-exact
decaps : NIST (dk,c) -> vendor K byte-exact
Verified against PQClean ML-KEM-768: 60/60 byte-exact (keyGen 25,
encaps 25, decaps 10). A deliberately sabotaged build of the same library
scores 5/60 — findings present.
Symbols are auto-detected. Derandomised entry points are required, because
NIST's vectors are seeded: a library exposing only randomised keygen can be
checked for self-consistency, which is not verification. In that case the tool
refuses and says so, rather than reporting a misleading pass.
Scope, restated
The ACVP suites validate pq-verify's own FIPS 203/204 reference chain
against NIST's published vectors — they do not audit a third party's
implementation. That distinction is now printed in every ACVP summary. Auditing
someone else's code is --audit-so (NTT symbol) or --audit-kem
(keygen/encaps/decaps).
Unchanged: not a constant-time analyser, FIPS 205 is key generation only, and
engines 5–6 are research mathematics rather than security checks.
Verification performed
- 18/18 pytest
- 160/160 self-suite with full coverage; 159/160 correctly reported when a
dependency is absent - 855/855 ACVP offline, 975/975 with
slhdsa=True - PQClean ML-KEM-768 full-KEM audit 60/60; sabotaged build 5/60
- Zero build warnings; both artifacts pass
twine check
Install
pip install "pq-verify[full]"
pq-verify --acvp-all # 855/855, offline
pq-verify --audit-kem lib.so ML-KEM-768 # audit someone else's KEMWhy this is in the release notes
A verification tool is only worth what its failure modes are worth knowing. The
four checks above were producing green results in released versions; they were
found by auditing pq-verify with the discipline pq-verify applies to others,
and each now has a test that prevents recurrence. Publishing that is the same
argument the tool makes: coverage you cannot see is coverage you do not have.
MIT · Nicholas Maino (iamweare) · DOI 10.5281/zenodo.21739511