pq-verify 2.6.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pq_verify-2.6.5/DEMO.ipynb +13 -0
- pq_verify-2.6.5/LICENSE +21 -0
- pq_verify-2.6.5/MANIFEST.in +7 -0
- pq_verify-2.6.5/PKG-INFO +231 -0
- pq_verify-2.6.5/QUICKSTART.md +85 -0
- pq_verify-2.6.5/README.md +199 -0
- pq_verify-2.6.5/pq_verify/__init__.py +54 -0
- pq_verify-2.6.5/pq_verify/cli.py +117 -0
- pq_verify-2.6.5/pq_verify/core.py +6107 -0
- pq_verify-2.6.5/pq_verify/report.py +207 -0
- pq_verify-2.6.5/pq_verify/vectors/MANIFEST.json +50 -0
- pq_verify-2.6.5/pq_verify/vectors/PROVENANCE.md +15 -0
- pq_verify-2.6.5/pq_verify/vectors/__init__.py +9 -0
- pq_verify-2.6.5/pq_verify/vectors/acvp_vectors.json.gz +0 -0
- pq_verify-2.6.5/pq_verify.egg-info/PKG-INFO +231 -0
- pq_verify-2.6.5/pq_verify.egg-info/SOURCES.txt +23 -0
- pq_verify-2.6.5/pq_verify.egg-info/dependency_links.txt +1 -0
- pq_verify-2.6.5/pq_verify.egg-info/entry_points.txt +2 -0
- pq_verify-2.6.5/pq_verify.egg-info/requires.txt +13 -0
- pq_verify-2.6.5/pq_verify.egg-info/top_level.txt +1 -0
- pq_verify-2.6.5/pyproject.toml +56 -0
- pq_verify-2.6.5/sample_report.json +44 -0
- pq_verify-2.6.5/setup.cfg +4 -0
- pq_verify-2.6.5/tests/test_pqverify.py +254 -0
- pq_verify-2.6.5/vendor_audit_template.py +142 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{"cell_type":"markdown","metadata":{},"source":["# pq-verify v2.6.0 — One-Click Demo\n","\n","Run all cells (Runtime -> Run all). ~3 minutes to 240/240 NIST ACVP vectors.\n","\n","Upload `pq_verify_v2_6_0.py` when the file picker appears in Cell 2."]},
|
|
4
|
+
{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["# Cell 1: dependencies (~60s)\n","!apt-get install -y -qq coq gcc g++ > /dev/null 2>&1\n","!pip install kyber-py -q --break-system-packages\n","print('dependencies installed')"]},
|
|
5
|
+
{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["# Cell 2: upload + load the stack (runs 160-test self-suite)\n","from google.colab import files\n","up = files.upload()\n","fn = list(up.keys())[0]\n","exec(open(fn).read())"]},
|
|
6
|
+
{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["# Cell 3: full NIST ACVP — all 12 groups, 240/240\n","pqverify_acvp()"]},
|
|
7
|
+
{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["# Cell 4: parameter security check\n","pqverify_params('ML-KEM-1024')"]},
|
|
8
|
+
{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["# Cell 5: native full-KEM at Level 5\n","pqverify_kem(k=4)"]},
|
|
9
|
+
{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["# Cell 6: audit a compiled .so (build a demo NTT, then audit it)\n","ntt_c = '''#include <stdint.h>\n","#define Q 3329\n","#define N 256\n","static uint16_t pw(uint16_t b,uint32_t e,uint16_t q){uint32_t r=1,x=b;while(e){if(e&1)r=r*x%q;x=x*x%q;e>>=1;}return(uint16_t)r;}\n","static uint32_t br(uint32_t x){uint32_t r=0;for(int i=0;i<7;i++){r=(r<<1)|(x&1);x>>=1;}return r;}\n","static uint16_t z[128];static int rdy=0;\n","void ntt(uint16_t f[N]){if(!rdy){for(int i=0;i<128;i++)z[i]=pw(17,br(i),Q);rdy=1;}int k=1;\n","for(int L=128;L>=2;L>>=1)for(int s=0;s<N;s+=2*L){uint16_t zz=z[k++];for(int j=s;j<s+L;j++){\n","uint16_t t=(uint16_t)(((uint32_t)zz*f[j+L])%Q);f[j+L]=(uint16_t)((f[j]+Q-t)%Q);f[j]=(uint16_t)((f[j]+t)%Q);}}}'''\n","open('kyber_ntt.c','w').write(ntt_c)\n","import os; os.system('gcc -O3 -shared -fPIC -o libkyber_ntt.so kyber_ntt.c')\n","ntt = pqverify_load_so('./libkyber_ntt.so','ntt')\n","pqverify_kat(ntt, k=4)\n","pqverify_leakage()"]}
|
|
10
|
+
],
|
|
11
|
+
"metadata":{"kernelspec":{"display_name":"Python 3","name":"python3"},"language_info":{"name":"python"}},
|
|
12
|
+
"nbformat":4,"nbformat_minor":0
|
|
13
|
+
}
|
pq_verify-2.6.5/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nicholas Maino (iamweare)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pq_verify-2.6.5/PKG-INFO
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pq-verify
|
|
3
|
+
Version: 2.6.5
|
|
4
|
+
Summary: Independent verification for ML-KEM / ML-DSA implementations — native field NTT verification, NIST ACVP, lattice parameter security, Coq certificates.
|
|
5
|
+
Author-email: Nicholas Maino <maiknown@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/bigDSanalyst/pq-verify
|
|
8
|
+
Project-URL: Repository, https://github.com/bigDSanalyst/pq-verify
|
|
9
|
+
Project-URL: Zenodo DOI, https://doi.org/10.5281/zenodo.19302050
|
|
10
|
+
Keywords: post-quantum,cryptography,ML-KEM,ML-DSA,Kyber,Dilithium,FIPS-203,FIPS-204,NIST,ACVP,NTT,verification,lattice
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Topic :: Security :: Cryptography
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Provides-Extra: full
|
|
21
|
+
Requires-Dist: kyber-py; extra == "full"
|
|
22
|
+
Requires-Dist: dilithium-py; extra == "full"
|
|
23
|
+
Requires-Dist: sympy; extra == "full"
|
|
24
|
+
Requires-Dist: slh-dsa; extra == "full"
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
27
|
+
Requires-Dist: kyber-py; extra == "dev"
|
|
28
|
+
Requires-Dist: dilithium-py; extra == "dev"
|
|
29
|
+
Requires-Dist: sympy; extra == "dev"
|
|
30
|
+
Requires-Dist: slh-dsa; extra == "dev"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# pq-verify v2.6.4 — PQC Implementation Verification
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+

|
|
37
|
+

|
|
38
|
+

|
|
39
|
+

|
|
40
|
+
[](https://doi.org/10.5281/zenodo.21739511)
|
|
41
|
+
|
|
42
|
+
**Independent verification for ML-KEM (Kyber) and ML-DSA (Dilithium) implementations.**
|
|
43
|
+
|
|
44
|
+
You deploy post-quantum cryptography. pq-verify proves your implementation computes the FIPS 203/204 standard correctly — in the native finite field, against NIST's own test vectors, with machine-checkable certificates. Plus FIPS 205 SLH-DSA parameter validation across all 12 parameter sets.
|
|
45
|
+
|
|
46
|
+
It does not compute PQC. It verifies the implementations that do: liboqs, BoringSSL, OpenSSL+OQS, HSM firmware, or your own code.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## What you get
|
|
51
|
+
|
|
52
|
+
A three-layer audit of any ML-KEM/ML-DSA implementation:
|
|
53
|
+
|
|
54
|
+
| Layer | Question answered | How |
|
|
55
|
+
|-------|-------------------|-----|
|
|
56
|
+
| **Correctness** | Does the NTT compute the FIPS definition? | Field-native verification + non-circular KAT |
|
|
57
|
+
| **Compliance** | Does it match NIST's published vectors? | ML-KEM 240/240 + ML-DSA 615/615 = 855/855 ACVP vectors (pinned) |
|
|
58
|
+
| **Security** | Are the parameters hard enough? | Bai-Galbraith primal-uSVP + hybrid attack estimator |
|
|
59
|
+
|
|
60
|
+
Plus per-layer side-channel leakage analysis with protection-allocation recommendations.
|
|
61
|
+
|
|
62
|
+
Every result is **reproducible** — deterministic output, SHA-256 fingerprint, re-runnable by your own auditors.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Proven (all tested on commodity hardware, Google Colab CPU)
|
|
67
|
+
|
|
68
|
+
- **160/160** self-test across 6 field-native engines, 6 phases
|
|
69
|
+
- **240/240** NIST ACVP ML-KEM vectors — keyGen + encaps + decaps byte-exact, KeyCheck bool-exact
|
|
70
|
+
- **Native full-KEM** verified at ML-KEM-1024 (Level 5): recovery 20/20, negative control caught
|
|
71
|
+
- **Non-circular KAT** 100/100 against the independent FIPS reference
|
|
72
|
+
- Calibrated lattice estimator: reproduces lattice-estimator exactly (Kyber-512 β=406/118.6 bits)
|
|
73
|
+
- **Coq certificates** verified by `coqc` with real exit codes
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Quick start
|
|
78
|
+
|
|
79
|
+
Open `DEMO.ipynb` in Google Colab and run all cells. ~3 minutes to 855/855.
|
|
80
|
+
|
|
81
|
+
Or, in any Python 3.8+ environment with gcc:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
exec(open('pq_verify_v2_6_1.py').read()) # 160-test self-suite + loads the API
|
|
85
|
+
|
|
86
|
+
pqverify_acvp() # full NIST ACVP, all parameter sets
|
|
87
|
+
pqverify_params('ML-KEM-1024') # parameter security check
|
|
88
|
+
pqverify_kem(k=4) # native full-KEM at Level 5
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
To audit your own compiled library:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
ntt = pqverify_load_so('/path/to/your_library.so', 'ntt_symbol')
|
|
95
|
+
pqverify_scan(ntt) # full audit + KAT + leakage
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
See `vendor_audit_template.py` for the complete "give us your .so → get a JSON report" workflow.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Public API
|
|
103
|
+
|
|
104
|
+
| Function | Purpose |
|
|
105
|
+
|----------|---------|
|
|
106
|
+
| `main()` | 160-test self-suite |
|
|
107
|
+
| `pqverify_acvp()` | Full NIST ACVP end-to-end ML-KEM (240/240, all groups) |
|
|
108
|
+
| `pqverify_mldsa_acvp()` | Full NIST ACVP end-to-end ML-DSA (615/615, FIPS 204) |
|
|
109
|
+
| `pqverify_slhdsa_acvp()` | NIST ACVP SLH-DSA keyGen (120/120, FIPS 205, all 12 parameter sets) |
|
|
110
|
+
| `pqverify_acvp_all()` | ML-KEM + ML-DSA (855/855) offline; `slhdsa=True` adds FIPS 205 → 975/975 |
|
|
111
|
+
| `pqverify_params(set)` | Parameter security: primal-uSVP + sparse hybrid |
|
|
112
|
+
| `pqverify_kem(k=4)` | Native algebraic full-KEM verification |
|
|
113
|
+
| `pqverify_kat(ntt, k=4)` | Non-circular KAT vs FIPS definition |
|
|
114
|
+
| `pqverify_load_so(path, sym)` | Load NTT from a compiled .so |
|
|
115
|
+
| `pqverify_scan(target)` | Auto-discover + audit NTT functions |
|
|
116
|
+
| `pqverify_leakage()` | Per-layer protection-allocation table |
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Deterministic by default
|
|
121
|
+
|
|
122
|
+
pq-verify ships with a **frozen, versioned snapshot of NIST's ACVP vectors** bundled
|
|
123
|
+
inside the package (gzipped, ~7 MB). By default it verifies against those — so:
|
|
124
|
+
|
|
125
|
+
- **the same input gives the same result, every run, forever**
|
|
126
|
+
- **it works with no network** — air-gapped, offline, no GitHub reachability needed
|
|
127
|
+
- **NIST editing their published files cannot change or break your result**
|
|
128
|
+
|
|
129
|
+
That last point is not hypothetical: NIST periodically regenerates these vectors and
|
|
130
|
+
has changed the ML-KEM `encapDecap` schema (the `keyFormat` seed/expanded split) more
|
|
131
|
+
than once. A tool that fetches live gives different answers on different days. This one
|
|
132
|
+
does not.
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
pqverify_acvp_all() # pinned bundle, offline, deterministic → 855/855
|
|
136
|
+
pqverify_acvp_all(live=True) # opt in: fetch NIST's current vectors instead
|
|
137
|
+
pqverify_acvp_all(vector_dir=d) # or point at your own local vector set
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Vector provenance and per-file sha256 are recorded in `pq_verify/vectors/MANIFEST.json`.
|
|
141
|
+
A scheduled GitHub Action watches upstream and opens an issue when NIST changes
|
|
142
|
+
something, so re-pinning is a deliberate, reviewed act rather than a live dependency.
|
|
143
|
+
|
|
144
|
+
## Scope
|
|
145
|
+
|
|
146
|
+
pq-verify verifies the **algebraic substance** of ML-KEM/ML-DSA (NTT, module-LWE relations, parameter security) natively in Z₃₃₂₉ / Z₈₃₈₀₄₁₇. The **non-algebraic layers** (SHAKE/SHA3 hashing, sampling, compression, the FO transform) are bit/byte operations verified by NIST ACVP end-to-end testing, not native field solving.
|
|
147
|
+
|
|
148
|
+
The algebraic core is proven natively where the proof is exact; the full implementation is proven byte-exact against NIST's own bytes. We make the claims we can prove.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## What's in this package
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
pq_verify/
|
|
156
|
+
__init__.py Public API (11 functions)
|
|
157
|
+
core.py The stack (~6,100 lines, 6 field-native engines)
|
|
158
|
+
cli.py Command-line interface
|
|
159
|
+
tests/test_pqverify.py 18-test pytest suite
|
|
160
|
+
pyproject.toml Build config + console-script entry point
|
|
161
|
+
dist/
|
|
162
|
+
pq_verify-2.6.4-py3-none-any.whl Installable wheel
|
|
163
|
+
pq_verify-2.6.4.tar.gz Source distribution
|
|
164
|
+
DEMO.ipynb One-click Colab demo → 855/855
|
|
165
|
+
vendor_audit_template.py Drop-in .so audit → JSON report
|
|
166
|
+
sample_report.json Example output (what your auditors receive)
|
|
167
|
+
README.md / QUICKSTART.md / LICENSE / CITATION.cff
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Install: `pip install dist/pq_verify-2.6.4-py3-none-any.whl`
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Requirements
|
|
175
|
+
|
|
176
|
+
**Minimum (core engines + ~149 self-tests):**
|
|
177
|
+
- Python 3.8+
|
|
178
|
+
- gcc and g++ (the C/C++ engines compile at runtime)
|
|
179
|
+
|
|
180
|
+
**For the full 160/160 self-suite and the 855/855 ACVP claim:**
|
|
181
|
+
- `kyber-py` — **required** for `pqverify_acvp()` (the byte-exact NIST reference) and the FIPS 203 roundtrip tests
|
|
182
|
+
- `dilithium-py` — **required** for `pqverify_mldsa_acvp()` (the 615 ML-DSA vectors)
|
|
183
|
+
- `coq` — required for the Coq certificate verification tests
|
|
184
|
+
- `sympy` — required for the Engine-6 Conjecture 7 exact-rational test (without it: 159/160)
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
apt-get install -y coq gcc g++
|
|
188
|
+
pip install kyber-py dilithium-py sympy --break-system-packages
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Optional (1 test each, everything works without them):**
|
|
192
|
+
- `cryptominisat` — the CMS5 speed-comparison benchmark
|
|
193
|
+
- `slh-dsa` — SLH-DSA live roundtrip (parameters still validate without it)
|
|
194
|
+
- network access — `pqverify_acvp()` fetches NIST vectors from GitHub live; for air-gapped use, pass `prompt_dir=` pointing at local vector files
|
|
195
|
+
|
|
196
|
+
**Deliberately NOT required** (a deployment advantage):
|
|
197
|
+
- No numpy, scipy, or PyTorch — pure Python + ctypes + inline C
|
|
198
|
+
- No SageMath — the `pqverify_params` lattice estimator is self-contained (it reproduces the lattice-estimator's results without it)
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## License
|
|
203
|
+
|
|
204
|
+
MIT. The verifier is open-source — builds trust, enables adoption. Commercial support, custom engine development, and PQC audit engagements available separately.
|
|
205
|
+
|
|
206
|
+
## Citing this software
|
|
207
|
+
|
|
208
|
+
Archived on Zenodo with a citable DOI:
|
|
209
|
+
|
|
210
|
+
> Maino, N. C. (2026). *pq-verify: Independent verification for ML-KEM / ML-DSA
|
|
211
|
+
> implementations* (v2.6.4). Zenodo. https://doi.org/10.5281/zenodo.21739511
|
|
212
|
+
|
|
213
|
+
```bibtex
|
|
214
|
+
@software{maino_pqverify_2026,
|
|
215
|
+
author = {Maino, Nicholas Clifford},
|
|
216
|
+
title = {pq-verify: Independent verification for ML-KEM / ML-DSA implementations},
|
|
217
|
+
version = {2.6.4},
|
|
218
|
+
year = {2026},
|
|
219
|
+
publisher = {Zenodo},
|
|
220
|
+
doi = {10.5281/zenodo.21739511},
|
|
221
|
+
url = {https://doi.org/10.5281/zenodo.21739511}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
The DOI above resolves to this specific release. The companion paper is
|
|
226
|
+
[10.5281/zenodo.19302050](https://doi.org/10.5281/zenodo.19302050).
|
|
227
|
+
|
|
228
|
+
## Contact
|
|
229
|
+
|
|
230
|
+
Nicholas Maino (iamweare) · maiknown@gmail.com · https://github.com/bigDSanalyst
|
|
231
|
+
Zenodo: https://doi.org/10.5281/zenodo.19302050
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# pq-verify v2.6.0 — Quickstart
|
|
2
|
+
|
|
3
|
+
## 1. Run the self-test (confirms the tool works on your machine)
|
|
4
|
+
|
|
5
|
+
Google Colab, or any Linux with `gcc` + Python 3.8+:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
apt-get install -y coq && pip install kyber-py dilithium-py sympy # for full 160/160 + 885/885
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
exec(open('pq_verify_v2_6_1.py').read())
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Expected: a 7-phase report ending in `OVERALL: 160/160 tests passed`.
|
|
16
|
+
Without coq/kyber-py/sympy you'll see fewer (those tests report as failures, not skips).
|
|
17
|
+
|
|
18
|
+
After loading, eight public functions are available: `main`, `pqverify_acvp`,
|
|
19
|
+
`pqverify_params`, `pqverify_kem`, `pqverify_kat`, `pqverify_load_so`,
|
|
20
|
+
`pqverify_scan`, and `pqverify_leakage`.
|
|
21
|
+
|
|
22
|
+
## 2. Verify your own implementation
|
|
23
|
+
|
|
24
|
+
### Option A — you have a compiled .so
|
|
25
|
+
|
|
26
|
+
Edit the CONFIG block in `vendor_audit_template.py`:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
SO_PATH = "/path/to/your_library.so"
|
|
30
|
+
NTT_SYMBOL = "ntt" # see "find your symbol" below
|
|
31
|
+
ALGORITHM = "ML-KEM-1024" # or ML-KEM-512/768, ML-DSA-44/65/87
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
exec(open('pq_verify_v2_6_1.py').read()) # load the engine
|
|
38
|
+
exec(open('vendor_audit_template.py').read()) # runs the audit
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Output: console report + `pqverify_vendor_report.json` (machine-readable,
|
|
42
|
+
with a reproducible SHA-256 fingerprint).
|
|
43
|
+
|
|
44
|
+
### Find your symbol (if you don't know it)
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
discover_symbols("/path/to/your_library.so")
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Lists exported NTT symbols. Note: many libraries inline the NTT as a
|
|
51
|
+
`static` function, so it won't be exported — in that case a small dedicated
|
|
52
|
+
`.so` exposing `void ntt(int16_t[256])` is the cleanest path.
|
|
53
|
+
|
|
54
|
+
### Option B — you have a Python implementation
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
exec(open('pq_verify_v2_6_1.py').read())
|
|
58
|
+
# your_ntt(list[int]) -> list[int] defined in this session
|
|
59
|
+
pqverify_scan(globals()) # auto-discovers and audits it
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 3. Individual checks
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
pqverify_kem(k=4) # native full-KEM, ML-KEM-1024 / Level 5
|
|
66
|
+
pqverify_kat(your_ntt, k=4) # non-circular KAT vs FIPS definition
|
|
67
|
+
pqverify_leakage() # per-layer protection-allocation table
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`k` selects the security level: 2 → Level 1, 3 → Level 3, 4 → Level 5
|
|
71
|
+
(Dilithium: 4/6/8 with `family='dilithium', q=8380417, zeta=1753`).
|
|
72
|
+
|
|
73
|
+
## 4. Reproducibility
|
|
74
|
+
|
|
75
|
+
The report is deterministic: same input → identical output → identical
|
|
76
|
+
SHA-256 fingerprint. Re-run any audit and compare fingerprints to confirm
|
|
77
|
+
the result wasn't tampered with.
|
|
78
|
+
|
|
79
|
+
## Notes
|
|
80
|
+
|
|
81
|
+
- Free Colab sessions are per-tab and reset after ~90 min idle. Keep the
|
|
82
|
+
`.py` files in Google Drive and `exec()` them from there to avoid re-uploading.
|
|
83
|
+
- Engines compile at runtime via gcc/g++ — first run takes a few seconds.
|
|
84
|
+
- Never install torchtext in the same Colab environment (unrelated, but it
|
|
85
|
+
corrupts the runtime).
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# pq-verify v2.6.4 — PQC Implementation Verification
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
[](https://doi.org/10.5281/zenodo.21739511)
|
|
9
|
+
|
|
10
|
+
**Independent verification for ML-KEM (Kyber) and ML-DSA (Dilithium) implementations.**
|
|
11
|
+
|
|
12
|
+
You deploy post-quantum cryptography. pq-verify proves your implementation computes the FIPS 203/204 standard correctly — in the native finite field, against NIST's own test vectors, with machine-checkable certificates. Plus FIPS 205 SLH-DSA parameter validation across all 12 parameter sets.
|
|
13
|
+
|
|
14
|
+
It does not compute PQC. It verifies the implementations that do: liboqs, BoringSSL, OpenSSL+OQS, HSM firmware, or your own code.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## What you get
|
|
19
|
+
|
|
20
|
+
A three-layer audit of any ML-KEM/ML-DSA implementation:
|
|
21
|
+
|
|
22
|
+
| Layer | Question answered | How |
|
|
23
|
+
|-------|-------------------|-----|
|
|
24
|
+
| **Correctness** | Does the NTT compute the FIPS definition? | Field-native verification + non-circular KAT |
|
|
25
|
+
| **Compliance** | Does it match NIST's published vectors? | ML-KEM 240/240 + ML-DSA 615/615 = 855/855 ACVP vectors (pinned) |
|
|
26
|
+
| **Security** | Are the parameters hard enough? | Bai-Galbraith primal-uSVP + hybrid attack estimator |
|
|
27
|
+
|
|
28
|
+
Plus per-layer side-channel leakage analysis with protection-allocation recommendations.
|
|
29
|
+
|
|
30
|
+
Every result is **reproducible** — deterministic output, SHA-256 fingerprint, re-runnable by your own auditors.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Proven (all tested on commodity hardware, Google Colab CPU)
|
|
35
|
+
|
|
36
|
+
- **160/160** self-test across 6 field-native engines, 6 phases
|
|
37
|
+
- **240/240** NIST ACVP ML-KEM vectors — keyGen + encaps + decaps byte-exact, KeyCheck bool-exact
|
|
38
|
+
- **Native full-KEM** verified at ML-KEM-1024 (Level 5): recovery 20/20, negative control caught
|
|
39
|
+
- **Non-circular KAT** 100/100 against the independent FIPS reference
|
|
40
|
+
- Calibrated lattice estimator: reproduces lattice-estimator exactly (Kyber-512 β=406/118.6 bits)
|
|
41
|
+
- **Coq certificates** verified by `coqc` with real exit codes
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Quick start
|
|
46
|
+
|
|
47
|
+
Open `DEMO.ipynb` in Google Colab and run all cells. ~3 minutes to 855/855.
|
|
48
|
+
|
|
49
|
+
Or, in any Python 3.8+ environment with gcc:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
exec(open('pq_verify_v2_6_1.py').read()) # 160-test self-suite + loads the API
|
|
53
|
+
|
|
54
|
+
pqverify_acvp() # full NIST ACVP, all parameter sets
|
|
55
|
+
pqverify_params('ML-KEM-1024') # parameter security check
|
|
56
|
+
pqverify_kem(k=4) # native full-KEM at Level 5
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
To audit your own compiled library:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
ntt = pqverify_load_so('/path/to/your_library.so', 'ntt_symbol')
|
|
63
|
+
pqverify_scan(ntt) # full audit + KAT + leakage
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
See `vendor_audit_template.py` for the complete "give us your .so → get a JSON report" workflow.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Public API
|
|
71
|
+
|
|
72
|
+
| Function | Purpose |
|
|
73
|
+
|----------|---------|
|
|
74
|
+
| `main()` | 160-test self-suite |
|
|
75
|
+
| `pqverify_acvp()` | Full NIST ACVP end-to-end ML-KEM (240/240, all groups) |
|
|
76
|
+
| `pqverify_mldsa_acvp()` | Full NIST ACVP end-to-end ML-DSA (615/615, FIPS 204) |
|
|
77
|
+
| `pqverify_slhdsa_acvp()` | NIST ACVP SLH-DSA keyGen (120/120, FIPS 205, all 12 parameter sets) |
|
|
78
|
+
| `pqverify_acvp_all()` | ML-KEM + ML-DSA (855/855) offline; `slhdsa=True` adds FIPS 205 → 975/975 |
|
|
79
|
+
| `pqverify_params(set)` | Parameter security: primal-uSVP + sparse hybrid |
|
|
80
|
+
| `pqverify_kem(k=4)` | Native algebraic full-KEM verification |
|
|
81
|
+
| `pqverify_kat(ntt, k=4)` | Non-circular KAT vs FIPS definition |
|
|
82
|
+
| `pqverify_load_so(path, sym)` | Load NTT from a compiled .so |
|
|
83
|
+
| `pqverify_scan(target)` | Auto-discover + audit NTT functions |
|
|
84
|
+
| `pqverify_leakage()` | Per-layer protection-allocation table |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Deterministic by default
|
|
89
|
+
|
|
90
|
+
pq-verify ships with a **frozen, versioned snapshot of NIST's ACVP vectors** bundled
|
|
91
|
+
inside the package (gzipped, ~7 MB). By default it verifies against those — so:
|
|
92
|
+
|
|
93
|
+
- **the same input gives the same result, every run, forever**
|
|
94
|
+
- **it works with no network** — air-gapped, offline, no GitHub reachability needed
|
|
95
|
+
- **NIST editing their published files cannot change or break your result**
|
|
96
|
+
|
|
97
|
+
That last point is not hypothetical: NIST periodically regenerates these vectors and
|
|
98
|
+
has changed the ML-KEM `encapDecap` schema (the `keyFormat` seed/expanded split) more
|
|
99
|
+
than once. A tool that fetches live gives different answers on different days. This one
|
|
100
|
+
does not.
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
pqverify_acvp_all() # pinned bundle, offline, deterministic → 855/855
|
|
104
|
+
pqverify_acvp_all(live=True) # opt in: fetch NIST's current vectors instead
|
|
105
|
+
pqverify_acvp_all(vector_dir=d) # or point at your own local vector set
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Vector provenance and per-file sha256 are recorded in `pq_verify/vectors/MANIFEST.json`.
|
|
109
|
+
A scheduled GitHub Action watches upstream and opens an issue when NIST changes
|
|
110
|
+
something, so re-pinning is a deliberate, reviewed act rather than a live dependency.
|
|
111
|
+
|
|
112
|
+
## Scope
|
|
113
|
+
|
|
114
|
+
pq-verify verifies the **algebraic substance** of ML-KEM/ML-DSA (NTT, module-LWE relations, parameter security) natively in Z₃₃₂₉ / Z₈₃₈₀₄₁₇. The **non-algebraic layers** (SHAKE/SHA3 hashing, sampling, compression, the FO transform) are bit/byte operations verified by NIST ACVP end-to-end testing, not native field solving.
|
|
115
|
+
|
|
116
|
+
The algebraic core is proven natively where the proof is exact; the full implementation is proven byte-exact against NIST's own bytes. We make the claims we can prove.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## What's in this package
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
pq_verify/
|
|
124
|
+
__init__.py Public API (11 functions)
|
|
125
|
+
core.py The stack (~6,100 lines, 6 field-native engines)
|
|
126
|
+
cli.py Command-line interface
|
|
127
|
+
tests/test_pqverify.py 18-test pytest suite
|
|
128
|
+
pyproject.toml Build config + console-script entry point
|
|
129
|
+
dist/
|
|
130
|
+
pq_verify-2.6.4-py3-none-any.whl Installable wheel
|
|
131
|
+
pq_verify-2.6.4.tar.gz Source distribution
|
|
132
|
+
DEMO.ipynb One-click Colab demo → 855/855
|
|
133
|
+
vendor_audit_template.py Drop-in .so audit → JSON report
|
|
134
|
+
sample_report.json Example output (what your auditors receive)
|
|
135
|
+
README.md / QUICKSTART.md / LICENSE / CITATION.cff
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Install: `pip install dist/pq_verify-2.6.4-py3-none-any.whl`
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Requirements
|
|
143
|
+
|
|
144
|
+
**Minimum (core engines + ~149 self-tests):**
|
|
145
|
+
- Python 3.8+
|
|
146
|
+
- gcc and g++ (the C/C++ engines compile at runtime)
|
|
147
|
+
|
|
148
|
+
**For the full 160/160 self-suite and the 855/855 ACVP claim:**
|
|
149
|
+
- `kyber-py` — **required** for `pqverify_acvp()` (the byte-exact NIST reference) and the FIPS 203 roundtrip tests
|
|
150
|
+
- `dilithium-py` — **required** for `pqverify_mldsa_acvp()` (the 615 ML-DSA vectors)
|
|
151
|
+
- `coq` — required for the Coq certificate verification tests
|
|
152
|
+
- `sympy` — required for the Engine-6 Conjecture 7 exact-rational test (without it: 159/160)
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
apt-get install -y coq gcc g++
|
|
156
|
+
pip install kyber-py dilithium-py sympy --break-system-packages
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Optional (1 test each, everything works without them):**
|
|
160
|
+
- `cryptominisat` — the CMS5 speed-comparison benchmark
|
|
161
|
+
- `slh-dsa` — SLH-DSA live roundtrip (parameters still validate without it)
|
|
162
|
+
- network access — `pqverify_acvp()` fetches NIST vectors from GitHub live; for air-gapped use, pass `prompt_dir=` pointing at local vector files
|
|
163
|
+
|
|
164
|
+
**Deliberately NOT required** (a deployment advantage):
|
|
165
|
+
- No numpy, scipy, or PyTorch — pure Python + ctypes + inline C
|
|
166
|
+
- No SageMath — the `pqverify_params` lattice estimator is self-contained (it reproduces the lattice-estimator's results without it)
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT. The verifier is open-source — builds trust, enables adoption. Commercial support, custom engine development, and PQC audit engagements available separately.
|
|
173
|
+
|
|
174
|
+
## Citing this software
|
|
175
|
+
|
|
176
|
+
Archived on Zenodo with a citable DOI:
|
|
177
|
+
|
|
178
|
+
> Maino, N. C. (2026). *pq-verify: Independent verification for ML-KEM / ML-DSA
|
|
179
|
+
> implementations* (v2.6.4). Zenodo. https://doi.org/10.5281/zenodo.21739511
|
|
180
|
+
|
|
181
|
+
```bibtex
|
|
182
|
+
@software{maino_pqverify_2026,
|
|
183
|
+
author = {Maino, Nicholas Clifford},
|
|
184
|
+
title = {pq-verify: Independent verification for ML-KEM / ML-DSA implementations},
|
|
185
|
+
version = {2.6.4},
|
|
186
|
+
year = {2026},
|
|
187
|
+
publisher = {Zenodo},
|
|
188
|
+
doi = {10.5281/zenodo.21739511},
|
|
189
|
+
url = {https://doi.org/10.5281/zenodo.21739511}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The DOI above resolves to this specific release. The companion paper is
|
|
194
|
+
[10.5281/zenodo.19302050](https://doi.org/10.5281/zenodo.19302050).
|
|
195
|
+
|
|
196
|
+
## Contact
|
|
197
|
+
|
|
198
|
+
Nicholas Maino (iamweare) · maiknown@gmail.com · https://github.com/bigDSanalyst
|
|
199
|
+
Zenodo: https://doi.org/10.5281/zenodo.19302050
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pq-verify — Independent verification for ML-KEM / ML-DSA implementations.
|
|
3
|
+
|
|
4
|
+
Verifies that post-quantum cryptography implementations compute the
|
|
5
|
+
FIPS 203/204 standard correctly: native field-native NTT verification,
|
|
6
|
+
non-circular Known Answer Tests, NIST ACVP end-to-end (270/270), a
|
|
7
|
+
Bai-Galbraith lattice parameter-security estimator, and per-layer
|
|
8
|
+
side-channel leakage analysis. Coq-certified, reproducible.
|
|
9
|
+
|
|
10
|
+
Author: Nicholas Maino (iamweare) — Melbourne AU
|
|
11
|
+
License: MIT
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
__version__ = "2.6.5"
|
|
15
|
+
__author__ = "Nicholas Maino (iamweare)"
|
|
16
|
+
__license__ = "MIT"
|
|
17
|
+
|
|
18
|
+
# Re-export the public API from the core engine (the real 5451-line stack).
|
|
19
|
+
from .core import (
|
|
20
|
+
main,
|
|
21
|
+
pqverify_kat,
|
|
22
|
+
pqverify_kem,
|
|
23
|
+
pqverify_acvp,
|
|
24
|
+
pqverify_mldsa_acvp,
|
|
25
|
+
pqverify_slhdsa_acvp,
|
|
26
|
+
pqverify_acvp_all,
|
|
27
|
+
pqverify_params,
|
|
28
|
+
pqverify_leakage,
|
|
29
|
+
pqverify_load_so,
|
|
30
|
+
pqverify_scan,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# FIPS 203 input-validation oracles (used by ACVP KeyCheck groups)
|
|
34
|
+
try:
|
|
35
|
+
from .core import check_encapsulation_key, check_decapsulation_key
|
|
36
|
+
except ImportError:
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"__version__",
|
|
41
|
+
"main",
|
|
42
|
+
"pqverify_kat",
|
|
43
|
+
"pqverify_kem",
|
|
44
|
+
"pqverify_acvp",
|
|
45
|
+
"pqverify_mldsa_acvp",
|
|
46
|
+
"pqverify_slhdsa_acvp",
|
|
47
|
+
"pqverify_acvp_all",
|
|
48
|
+
"pqverify_params",
|
|
49
|
+
"pqverify_leakage",
|
|
50
|
+
"pqverify_load_so",
|
|
51
|
+
"pqverify_scan",
|
|
52
|
+
"check_encapsulation_key",
|
|
53
|
+
"check_decapsulation_key",
|
|
54
|
+
]
|