Skip to content

pq-verify v2.6.7

Choose a tag to compare

@bigDSanalyst bigDSanalyst released this 16 Aug 05:47
· 136 commits to main since this release
8ffe486

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-768

This 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 KEM

Why 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