Repository navigation
pq-verify v2.8.0
Added
-
--verify-hybrid/--emit-hybrid-prompt— RFC 10024 hybrid key
agreement. Nothing in production negotiates bare ML-KEM; every deployment
that has turned post-quantum TLS on runs a hybrid group. The ML-KEM half is
covered by--acvpand--audit-kem. The composition was not, and the
composition is where the bugs are, because RFC 10024 does not use one order:Group Codepoint Key share Shared secret X25519MLKEM7680x11ECML-KEM ‖ ECDHE ML-KEM ‖ ECDHE SecP256r1MLKEM7680x11EBECDHE ‖ ML-KEM ECDHE ‖ ML-KEM SecP384r1MLKEM10240x11EDECDHE ‖ ML-KEM ECDHE ‖ ML-KEM The first row is reversed relative to its own name — the RFC says so and
calls it historical. An implementation can pass every ACVP vector
byte-for-byte and still be wrong, because ACVP never sees the
concatenation, and the failure is silent: two peers that make the same
mistake interoperate with each other and with nobody else.From one handshake's wire bytes,
--verify-hybridchecks the lengths, the
component split, the FIPS 203 §7.2 encapsulation key check the RFC makes a
MUST, ECDHE point validity (RFC 9846 §4.3.8.2), the X25519 all-zero check,
and — given an ephemeral private scalar — the ECDHE shared secret
recomputed and compared byte-for-byte at the pinned offset.When a check fails it tests the other order explicitly and says so: "there
is no valid encapsulation key at offset 0, but there IS one at the offset
the other order gives". That is a root cause rather than a mismatch, and it
is sound rather than heuristic — random bytes pass the FIPS 203 §7.2 check
with probability below 2⁻¹⁴⁰.New rule
PQV007(HybridCompositionMismatch). The report is
artifact: none — vendor-supplied transcript, on the same terms as
--verify-response. No private KEM key is ever requested. -
side_channelon every machine-readable report. Functional conformance
and leakage are independent properties, and this tool only measures the
first. KyberSlash and Clangover were byte-exact correct against every vector
and still recovered secret material through timing; a tool that checked only
what pq-verify checks would have passed both. Rather than leave that to be
inferred, every native report and the SARIF output now carry
"measured": falsewith the reason. Native report schemas move to
schema_version1.1.
Changed
--leakageis described as what it is. It was presented as
"side-channel leakage analysis", which a reader hears as a measurement. It
is an algebraic protection allocation: it computes, from the NTT's
structure, how much of the secret each butterfly layer would determine if
that layer's intermediates were exposed. Nothing is executed under
observation and no trace is collected. The computation is unchanged; the
claim around it now matches it.
Fixed
- QUICKSTART documented a file that has not existed since this became a pip
package. It told readers toexec(open('pq_verify_v2_6_1.py').read()),
listed "eight public functions", and gave the floor as Python 3.8 — below
the declaredrequires-python. Rewritten against what the tool actually
does, and three guards added: every flag shown on apq-verifycommand line
must be one the parser accepts, every file the docs name must exist, and a
documented Python floor must equalrequires-python. All three were
confirmed by reintroducing the exact defects. - A stale line count in
pq_verify/__init__.py("the real 5451-line stack"),
deleted rather than corrected — a number nothing computes will drift again.
Verification
- 49 new tests (83 → 141), green on 3.9, 3.12 and 3.13.
- The ECDH and X25519 reference is checked against RFC 7748 §5.2/§6.1 and the
NIST CAVS 14.1 ECC CDH vectors before it is used to judge any transcript; a
verifier whose own arithmetic is wrong would score a correct transcript as
broken. - The curve parameters are self-validating at import (base point on the curve,
n·G at infinity), so a mistyped constant cannot reach a verdict. - The FIPS 203 §7.2 check agrees with all 20 of NIST's own labelled
encapsulationKeyCheckcases for ML-KEM-768 and ML-KEM-1024. - 14 mutations were applied to the new guards — reversed orders, a disabled
modulus bound, each wrong-order diagnostic removed, skips counted as passes,
the side-channel field dropped. All 14 were caught. One initially survived:
the swapped-shared-secret test asserted over the findings as a whole, so the
ML-KEM diagnostic covered for the disabled ECDHE one. The test now asserts
per check.
Verifying this release
gh attestation verify pq_verify-2.8.0-py3-none-any.whl \
--repo bigDSanalyst/pq-verifyBuilt by .github/workflows/release.yml from commit 1884b49bd1573011d6bee6852fcdeae5ec6859c6,
after the full suite and all 855 NIST ACVP vectors passed on Python 3.9 through 3.13.
An SPDX SBOM is attached and attested.