Lattice-based post-quantum cryptography playground focused on clear, testable implementations of ML-KEM and ML-DSA.
Current status: v0.1.0 released.
- Working ML-KEM flow: key generation, encapsulation, decapsulation
- Working ML-DSA flow: key generation, signing, verification
- Reproducible deterministic demos for both schemes
- Automated CI, CodeQL, release workflow, and coverage publication
- Core algebra primitives in
src/core:- integer rings
- polynomial rings and quotient polynomial rings
- module arithmetic
- serialization helpers
- NTT and sampling utilities
- ML-KEM PKE foundation in
src/schemes/ml_kem:- key generation
- encryption/decryption
- parameter presets (
ML-KEM-512,ML-KEM-768,ML-KEM-1024) - deterministic matrix expansion helpers
- ciphertext compression/decompression (
c1/c2flow) - compatibility branch for legacy
u/vciphertext payloads during transition
- ML-KEM KEM layer in
src/schemes/ml_kem:ml_kem_keygenml_kem_encapsml_kem_decaps- FO-style hash helpers (
G,H,J)
- ML-DSA layer in
src/schemes/ml_dsa:ml_dsa_keygenml_dsa_signml_dsa_verify- parameter presets (
ML-DSA-44,ML-DSA-65,ML-DSA-87) - Power2Round split (
t1public,t0secret) - hint-based signing/verification flow (
MakeHint/UseHintstyle)
This first release ships a working, tested, educational implementation of:
- ML-KEM: keygen, encaps, decaps
- ML-DSA: keygen, sign, verify
- deterministic seed-driven flows for reproducible tests
- extensive unit and integration tests
- CI automation for multi-Python validation and release packaging
src/schemes/ml_kem and src/schemes/ml_dsa are the active focus. Communication and experiment layers are scaffolded and intentionally minimal in v0.1.0.
src/
core/
integers.py # integer ring arithmetic
polynomials.py # polynomial ring operations
module.py # module arithmetic
ntt.py # number-theoretic transform
sampling.py # sampling utilities (CBD, uniform)
serialization.py # byte serialization helpers
schemes/
utils.py # shared utilities (CRH, XOF, PRF)
ml_kem/
kyber_pke.py # PKE foundation layer
pke_utils.py # PKE helper functions
vectors.py # matrix/vector definitions
params.py # ML-KEM parameter presets
kyber_ntt.py # NTT-based operations for ML-KEM
kyber_sampling.py # ML-KEM-specific sampling
keygen.py # KEM key generation
encaps.py # KEM encapsulation
decaps.py # KEM decapsulation
hashes.py # G/H/J hash and derivation functions
ml_kem.py # canonical high-level exports
ml_dsa/
params.py # ML-DSA parameter presets
keygen.py # key generation
sign.py # signing logic
verify.py # verification logic
sign_verify_utils.py # signing/verification utilities
ml_dsa.py # canonical high-level exports
comms/ # scaffolding
experiments/ # scaffolding
app/ # scaffolding
tests/
core/
test_*.py # core algebra tests
schemes/
ml_kem/
test_*.py # ML-KEM tests
ml_dsa/
test_*.py # ML-DSA tests
integration/
test_*.py # end-to-end tests
conformance/
test_*.py # KAT conformance suites
python3 scratch.pyThis runs both ML-KEM and ML-DSA demos in one command.
python3 demos/ml_kem_demo.py
python3 demos/ml_dsa_demo.pyfrom src.schemes.ml_kem.kyber_pke import (
kyber_pke_keygen,
kyber_pke_encryption,
kyber_pke_decryption,
)
params = "ML-KEM-768"
pk, sk = kyber_pke_keygen(params)
message = b"0123456789abcdef0123456789abcdef" # 32 bytes required
ciphertext = kyber_pke_encryption(pk, message, params=params, coins=b"a" * 32)
recovered = kyber_pke_decryption(ciphertext, sk, params=params)
assert recovered == messageRun the full test suite (recursive discovery):
python3 -m unittest discover -s tests -p 'test_*.py'Run only core math tests:
python3 -m unittest discover -s tests/core -p 'test_*.py'Run only ML-KEM scheme tests:
python3 -m unittest discover -s tests/schemes/ml_kem -p 'test_*.py'Run integration-style tests:
python3 -m unittest discover -s tests/integration -p 'test_*.py'Run a single scheme test module while iterating:
python3 -m unittest tests/schemes/ml_dsa/test_ml_dsa_sign.pyThe repository includes vector-based conformance suites for ML-KEM and ML-DSA:
tests/conformance/test_ml_kem_kat.pytests/conformance/test_ml_dsa_kat.py
Run both KAT suites in strict full-vector mode:
LIBPQC_KAT_MAX_RECORDS=1000 LIBPQC_KAT_REQUIRE_FULL=1 \
python3 -m unittest tests/conformance/test_ml_kem_kat.py tests/conformance/test_ml_dsa_kat.pyRun quick KAT smoke checks (2 records per vector file) with progress + timing:
LIBPQC_KAT_MAX_RECORDS=2 LIBPQC_KAT_PROGRESS=1 LIBPQC_KAT_TIMING=1 \
python3 -m unittest tests/conformance/test_ml_kem_kat.py tests/conformance/test_ml_dsa_kat.pyGenerate a per-vector conformance summary (pass/fail, processed count, elapsed):
python3 scripts/conformance_summary.py --max-records 2Full-vector summary mode:
python3 scripts/conformance_summary.py --max-records 1000 --fullUseful runtime controls:
LIBPQC_KAT_MAX_RECORDS: max records to process per vector fileLIBPQC_KAT_REQUIRE_FULL: enforce processing of every record in each fileLIBPQC_KAT_PROGRESS: print per-file progress countersLIBPQC_KAT_TIMING: print per-file elapsed seconds in completion outputLIBPQC_KAT_VECTOR_FILTER: optional regex filter for vector file names
CI behavior:
- Pull requests and non-scheduled CI runs execute reduced conformance smoke mode (
LIBPQC_KAT_MAX_RECORDS=2). - Nightly scheduled CI executes strict full-vector conformance (
LIBPQC_KAT_MAX_RECORDS=1000+LIBPQC_KAT_REQUIRE_FULL=1).
For details on conformance helpers, adapter layers, and suggested folder architecture,
see tests/conformance/README.md.
As of 2026-04-02, both conformance suites pass against the currently checked-in
vector corpus (tests/conformance/vectors/ml_kem/*.rsp and
tests/conformance/vectors/ml_dsa/*.rsp).
Verified runs:
- default mode
python3 -m unittest tests/conformance/test_ml_kem_kat.py tests/conformance/test_ml_dsa_kat.py- result:
Ran 6 tests ... OK
- strict full-vector mode
LIBPQC_KAT_MAX_RECORDS=1000 LIBPQC_KAT_REQUIRE_FULL=1 python3 -m unittest tests/conformance/test_ml_kem_kat.py tests/conformance/test_ml_dsa_kat.py- result:
Ran 6 tests ... OK
What this currently guarantees:
- ML-KEM vector comparisons pass for packed public key, packed secret key, ciphertext bytes, and shared secret checks.
- ML-DSA vector comparisons pass for packed verification/signing keys,
signature bytes, and verification acceptance for the vector-specific message
domain handling (
raw,pure,hashed, and hedged/deterministic modes).
Generate coverage data and reports:
coverage erase
coverage run -m unittest discover -s tests -p 'test_*.py'
coverage json -o coverage/coverage.json
coverage html -d coverage/html
coverage xml -o coverage/coverage.xml
python3 scripts/update_coverage_assets.pyUseful outputs:
coverage/summary.mdcoverage/html/index.htmlcoverage/badge.svg
This repo includes a release workflow in .github/workflows/release.yml.
For a tag release:
git checkout main
git pull --ff-only
git tag v0.1.0
git push origin v0.1.0The workflow runs tests and publishes a source tarball in GitHub Releases.
For release notes, see CHANGELOG.md.
- Imports should use the canonical
src.*paths. - Messages for current Kyber-PKE helpers are fixed at 32 bytes.
- The repository favors explicit, testable building blocks over tightly coupled abstractions.
Implemented in this release:
- fully working ML-KEM and ML-DSA core flows
- high coverage and CI automation
- deterministic demo scripts for quick validation
Next priorities:
- performance profiling and optional optimized paths
- richer protocol-level examples (key exchange + signed channel skeleton)
- API stabilization and packaging improvements
- packaging and distribution ergonomics (CLI/docs/publish flow)
This project is licensed under the terms in LICENSE.