pqcrypto 0.3.1
pqcrypto: ^0.3.1 copied to clipboard
Pure Dart post-quantum cryptography. Zero dependencies. Features byte-exact FIPS 203 ML-KEM & FIPS 204 ML-DSA for secure Flutter & Web apps.
pqcrypto: Pure Dart Post-Quantum Cryptography #
pqcrypto is a pure Dart library implementing Post-Quantum Cryptography (PQC) algorithms, targeting compatibility with Flutter and the Dart web ecosystem.
The supported release surface provides a FIPS 203-aligned implementation of ML-KEM (Kyber) and a FIPS 204-aligned implementation of ML-DSA (Dilithium), each with checked-in known-answer tests and focused unit coverage. ML-KEM additionally carries OpenSSL interoperability evidence. ML-DSA is byte-exact against the official FIPS 204 KAT corpus across every parameter set, signing mode, and implementation flavour (see below). Neither algorithm claims CMVP/FIPS 140 module validation โ see doc/FIPS_140_BOUNDARY.md for exactly what is and is not claimed.
๐ Features #
- FIPS 203-aligned ML-KEM support:
- Algorithm Support: ML-KEM-512, ML-KEM-768, ML-KEM-1024
- Secure Primitives:
- SHAKE-128/256 based matrix generation and hashing.
- Centered Binomial Distribution (CBD) for secure noise sampling.
- Key Encapsulation:
(rho, sigma) := G(d || k)derivation for K-PKE key generation. - Fujisaki-Okamoto Transform: Robust re-encryption check to prevent chosen-ciphertext attacks (IND-CCA2 security).
- Input Checks: Public-key length/modulus checks, decapsulation-key length/hash checks, and ciphertext length checks.
- FIPS 204-aligned ML-DSA support:
- Algorithm Support: ML-DSA-44, ML-DSA-65, ML-DSA-87 (internal, external, and HashML-DSA).
- Hedged-by-default signing with explicit deterministic and pre-hash paths and FIPS 204 context strings (โค 255 bytes).
- Byte-exact against the official FIPS 204 KAT corpus (1800 signatures + 300 key generations) and vendored SHA-2 (SHA-256/384/512) for HashML-DSA pre-hashing.
- Defensive verification: returns
false(never throws) on malformed public keys, signatures, hints, or over-long contexts; unbounded XOF rejection sampling; best-effort secret zeroization.
- Platform Agnostic:
- 100% Pure Dart. Works on Android, iOS, Windows, Linux, macOS, and Web (dart2js/dart2wasm) โ verified on all three backends in CI.
- Zero dependencies. No third-party packages at all: FIPS 202 (SHA3-256/512, SHAKE128/256) is vendored in-tree, so
lib/depends only ondart:typed_data.
๐ก๏ธ ML-KEM Validation Status #
This implementation tracks FIPS 203, but this repository does not claim CMVP/FIPS 140 module validation (why?). The current evidence is the checked-in KAT corpus plus unit tests for the algorithm surfaces listed below.
| Algorithm | Status | Checked-in KAT Vectors | Security Level |
|---|---|---|---|
| ML-KEM-512 | KAT pass | 1000/1000 PASS | NIST Level 1 (AES-128) |
| ML-KEM-768 | KAT pass | 1000/1000 PASS | NIST Level 3 (AES-192) |
| ML-KEM-1024 | KAT pass | 1000/1000 PASS | NIST Level 5 (AES-256) |
Total checked-in vectors: 3000/3000 pass locally as of June 3, 2026.
See doc/MLKEM_TESTING.md for the KAT file hashes, coverage boundaries, and release-gate commands.
๐ก๏ธ ML-DSA Validation Status #
This implementation tracks FIPS 204.
As with ML-KEM, this repository does not claim CMVP/FIPS 140 module
validation (why?); the evidence is the checked-in KAT
corpus plus focused unit tests. The package exports MlDsa, DilithiumParams,
and DilithiumParameter.
Every signature in the official KAT corpus (test/data/MLDSA) is reproduced
byte-for-byte, and every KAT signature verifies, across the full matrix
of parameter set ร signing mode ร implementation flavour:
| Parameter set | Security level | KeyGen (raw/det) | Sign + Verify (all flavours) |
|---|---|---|---|
| ML-DSA-44 | NIST Level 2 | 100/100 PASS | 600/600 PASS |
| ML-DSA-65 | NIST Level 3 | 100/100 PASS | 600/600 PASS |
| ML-DSA-87 | NIST Level 5 | 100/100 PASS | 600/600 PASS |
- Flavours:
raw(internal*_internal, Algorithms 6/7/8),pure(external ML-DSA with a context string, Algorithms 1/2/3), andhashed(HashML-DSA with SHA-256/384/512 pre-hash, Algorithms 1/4/5). - Modes:
deterministic(rnd = 0) andhedged(rndfrom the vector). - Totals: 300/300 byte-exact key generations and 1800/1800 byte-exact signatures that all verify (3 levels ร 2 modes ร 3 flavours ร 100 vectors).
The public API is hedged by default (fresh rnd from Random.secure()),
supports FIPS 204 context strings (โค 255 bytes), exposes explicit deterministic
and HashML-DSA paths, and returns false (never throws) for any malformed
public key, signature, hint, or over-long context. See the controlling guide
doc/MLDSA_FIPS204_RELEASE_GUIDE.md and the
corpus description in test/data/MLDSA/README.md.
final params = DilithiumParams.mlDsa65;
final (pk, sk) = MlDsa.generateKeyPair(params); // fresh randomness
final sig = MlDsa.sign(sk, message, params, ctx: appCtx); // hedged by default
final ok = MlDsa.verify(pk, message, sig, params, ctx: appCtx);
๐ OpenSSL Interoperability #
pqcrypto's ML-KEM is wire-compatible with OpenSSL's native ML-KEM at all
three parameter sets โ ML-KEM-512, ML-KEM-768, and ML-KEM-1024. OpenSSL
exposes those native ML-KEM algorithms in the 3.5 line and newer; the local
interop harness (tool/openssl_interop/) drives both
implementations over dart:ffi and proves byte-level agreement on each:
| Test | What it proves |
|---|---|
| A / B | each implementation is internally self-consistent (sanity) |
| C | OpenSSL decapsulates a pqcrypto ciphertext โ same secret (fuzzed) |
| D | pqcrypto decapsulates an OpenSSL ciphertext โ same secret (fuzzed) |
| E | same seed (dโz) โ byte-identical public keys |
| F | public-key wire round-trip (pqcrypto โ OpenSSL โ bytes) is identical |
| G | implicit-rejection secret J(zโc) agrees on an invalid ciphertext |
Shared secrets โ including the FIPS 203 implicit-rejection branch โ are byte-identical across implementations in both directions, at every level (public keys and ciphertexts are standardized raw encodings, so no format conversion is needed).
The library stays 100% pure Dart. This interop check is a developer/CI tool under
tool/that usesdart:ffito call OpenSSL'slibcrypto. It is not part of thepqcryptopackage or its dependencies โlib/imports no FFI, nothing native ships to consumers, and the tool is excluded from the published package (see.pubignore).
- Linux: verified against OpenSSL 3.5.4 and 3.5.6 (Dart 3.12.0) on 2026-06-03; CI also builds and runs OpenSSL 4.0.0.
- macOS: runs against Homebrew OpenSSL โฅ 3.5 (
brew install openssl@3.5).
cd tool/openssl_interop
dart pub get
dart test # rigorous suite: tests AโG ร all three levels
dart run bin/openssl_pqcrypto_interop.dart # human-readable harness
# Linux: prefix LIBCRYPTO_PATH=/path/to/libcrypto.so (OpenSSL >= 3.5)
CI runs the full suite on every push via
.github/workflows/interop.yml.
Full details: doc/OPENSSL_INTEROP.md โ FFI
bindings, the FIPS 203 fixes interop required, exact versions/results, and use
cases (hybrid TLS X25519MLKEM768, Dart โ OpenSSL services, migration).
๐ ๏ธ Implementation Highlights #
This library follows the FIPS 203 and FIPS 204 specification structures where practical while keeping validation claims scoped to the tests in this repository. ML-KEM and ML-DSA use separate polynomial types, moduli, NTTs, packing code, and parameter objects so the two lattice schemes do not share algorithm-specific arithmetic accidentally.
1. ML-KEM Number Theoretic Transform (NTT) #
Uses pure modular arithmetic (not Montgomery) matching the FIPS 203 Algorithms 8 and 9:
- NTT/InvNTT: Cooley-Tukey butterfly operations with modular reduction.
- Base Multiplication: Karatsuba-style in NTT domain using $\gamma$ coefficients (Algorithm 10).
- Polynomial Ring: Operations in $\mathbb{Z}_q[X]/(X^{256}+1)$ where $q = 3329$.
2. Compression & Serialization #
Compression functions follow FIPS 203 Definitions 4.7-4.8:
- compress(x, d): Standard rounding logic $\lceil (2^d/q) \cdot x \rfloor \bmod 2^d$.
- Formula:
(2 * x * 2^d + q) / (2 * q)with modulo2^dwrap at the boundary. - ByteEncode support:
- 12-bit: Public Keys (
ByteEncodeโโ) - 11-bit: ML-KEM-1024 Ciphertext $u$ (
ByteEncodeโโ) - 10-bit: ML-KEM-768 Ciphertext $u$ (
ByteEncodeโโ) - 5-bit: ML-KEM-1024 Ciphertext $v$ (
ByteEncodeโ) - 4-bit: ML-KEM-512/768 Ciphertext $v$ (
ByteEncodeโ) - 1-bit: Messages (
ByteEncodeโ)
- 12-bit: Public Keys (
3. ML-DSA Signature Architecture #
ML-DSA lives under lib/src/algos/dilithium/ and tracks FIPS 204's external
and internal function split:
- Public API layering:
MlDsa.generateKeyPair,sign, andverifyexpose external ML-DSA Algorithms 1-3;hashSignandhashVerifyexpose HashML-DSA Algorithms 4-5;generateKeyPairSeeded,signInternal, andverifyInternalremain available for deterministic KAT/CAVP-style vectors. - Parameter sets:
DilithiumParamscarries ML-DSA-44/65/87 parameters, FIPS 204 Table 2 public-key, secret-key, and signature sizes, and the per-leveltau,gamma1,gamma2,omega, and challenge length values. - Hedged-by-default signing: external signing draws a fresh 32-byte
rndfromRandom.secure; deterministic signing is explicit throughsignDeterministicor a supplied KATrnd. - FIPS 204 message domains: external ML-DSA signs
0x00 || len(ctx) || ctx || M; HashML-DSA signs0x01 || len(ctx) || ctx || DER(OID(PH)) || PH(M). - Signing core: key generation expands
xiintorho,rho', andK, buildsA, sampless1/s2, appliesPower2Round, and packspk/sk; signing derivesmu, samplesy, computes the challenge, applies rejection checks, builds hints, and packs(c_tilde, z, h). - Verification core: verification reconstructs
w1', re-hashesmu || w1Encode(w1'), and compares challenge hashes without early exit.
4. ML-DSA Arithmetic, Sampling & Encoding #
ML-DSA uses a different ring from ML-KEM:
- Polynomial Ring: operations in $\mathbb{Z}_q[X]/(X^{256}+1)$ where $q = 8380417$.
- Complete NTT:
DilithiumNTTuses the FIPS 204 Appendix B zeta table and a complete coefficient-wise NTT shape, separate from ML-KEM's incomplete NTT and base-multiplication path. - Sampling:
ExpandAuses SHAKE-128 rejection sampling for NTT-domain matrix coefficients;ExpandS,ExpandMask, andSampleInBalluse SHAKE-256 with unbounded XOF squeezing so the samplers do not exhaust fixed buffers. - Signed-domain packing:
packing.darthandles public keys, secret keys, signatures,t0/t1,z, and sparse hints with FIPS 204-derived sizes. - HashML-DSA pre-hash: vendored SHA-256/384/512 are selected by level (ML-DSA-44/65/87) and paired with the DER OID bytes required by FIPS 204 domain separation.
5. Cryptographic Primitives #
- ML-KEM XOF/PRF: SHAKE-128 for FIPS 203 matrix/sample generation and SHAKE-256 for noise sampling.
- ML-KEM hash functions: SHA3-256 and SHA3-512 for FIPS 203 key derivation, ciphertext binding, and implicit rejection.
- ML-KEM CBD Sampling: Centered Binomial Distribution with $\eta \in {2,3}$.
- ML-DSA XOF/CRH: SHAKE-128 for
ExpandA; SHAKE-256 forH,G,CRH, bounded sampling, mask expansion, and challenge sampling. - Vendored FIPS 202: SHA-3 and SHAKE are implemented in-tree (
lib/src/common/keccak.dart) with no third-party dependency, using web-safe 32-bit lane arithmetic verified on the VM,dart2js, anddart2wasm. - Vendored FIPS 180-4: SHA-256/384/512 are implemented in-tree
(
lib/src/common/sha2.dart) for HashML-DSA pre-hashing, using 32-bit word pairs for SHA-384/512 portability across the VM and web compilers.
6. Security Hardening #
- Implicit Rejection: Implementation of the modified Fujisaki-Okamoto transform guarantees that invalid ciphertexts produce a pseudo-random shared secret (derived from internal secret $z$) rather than failing. This prevents chosen-ciphertext timing attacks.
- Constant-time output selection: ML-KEM decapsulation always computes both the re-encryption secret
K'and the implicit-rejection secretJ(zโc)and selects between them with a branchless mask, so success vs. rejection does not leak through control flow. - Best-effort zeroization: secret intermediates in ML-KEM decapsulation and ML-DSA key generation / signing are overwritten in
finallyblocks (lib/src/common/zeroize.dart); see doc/SECURITY_AUDIT.md for the Dart limitations. - ML-DSA norm checks:
_normExceedsscans all 256 coefficients with no early exit; residual branch-direction timing remains documented as best-effort Dart hardening, not a constant-time proof. - Domain Separation: ML-KEM uses the standardized FIPS 203 hash/XOF inputs; ML-DSA and HashML-DSA include FIPS 204 domain bytes, context length, context, and HashML-DSA OID/domain material.
- Input Validation:
encapsulaterejects malformed public keys, anddecapsulaterejects malformed secret keys or ciphertext lengths before running decapsulation. ML-DSA verification returnsfalsefor malformed public keys, signatures, hints, norm violations, or over-long contexts. - Validation evidence:
test/kat_evaluator_test.dartcovers the checked-in ML-KEM corpus;test/mldsa_kat_test.dartcovers all 18 ML-DSA KAT files across raw/pure/hashed and deterministic/hedged signing.
๐ Project Structure #
lib/
โโโ pqcrypto.dart # ๐ฆ Library Entrypoint
โโโ src/
โโโ algos/
โ โโโ kyber/ # ๐ ML-KEM (FIPS 203) implementation
โ โ โโโ kem.dart # ๐ High-level API + KeyGen/Encaps/Decaps (Alg 15-18)
โ โ โโโ indcpa.dart # ๐ IND-CPA K-PKE core (Algorithms 12-14)
โ โ โโโ pack.dart # ๐พ ByteEncode/Decode + Compress (Algs 4-5)
โ โ โโโ params.dart # ๐ ML-KEM params (k, eta1, eta2, du, dv)
โ โ
โ โโโ dilithium/ # โ๏ธ ML-DSA (FIPS 204) implementation
โ โโโ dsa.dart # ๐๏ธ MlDsa: external + internal + HashML-DSA
โ โ # KeyGen/Sign/Verify (Algs 1-3), *_internal
โ โ # (Algs 6-8), HashML-DSA sign/verify (Algs 4-5)
โ โโโ params.dart # ๐ ML-DSA-44/65/87 params + Table 2 sizes
โ โโโ poly.dart # ๐งฎ DilithiumPoly / vector types (q=8380417)
โ โโโ ntt.dart # ๐ Complete NTT + Appendix B zetas
โ โโโ packing.dart # ๐พ pk/sk/sig encode/decode (signed domains)
โ โโโ rounding.dart # ๐ Power2Round/Decompose/MakeHint/UseHint
โ โโโ symmetric.dart # ๐ฒ ExpandA/S, ExpandMask, SampleInBall, pre-hash
โ
โโโ common/
โโโ poly.dart # ๐งฎ ML-KEM Polynomial Arithmetic & NTT
โ # - NTT / InvNTT (Algorithms 8-9)
โ # - BaseMul [MultiplyNTTs] (Algorithm 10)
โ # - SampleNTT [Parse] (Algorithm 7)
โ # - PolyAdd, PolySub, PolyReduce
โ
โโโ shake.dart # ๐ฒ SHAKE-128/256 wrappers + incremental XOF
โ
โโโ keccak.dart # ๐งฑ Vendored FIPS 202 (zero-dependency)
โ # - SHA3-256/512, SHAKE128/256, KeccakXof
โ # - web-safe 32-bit lanes (dart2js/dart2wasm)
โ
โโโ sha2.dart # #๏ธโฃ Vendored FIPS 180-4 SHA-256/384/512
โ # - HashML-DSA pre-hash; web-safe 64-bit pairs
โ
โโโ zeroize.dart # ๐งน Best-effort secret zeroization helpers
test/
โโโ kat_evaluator_test.dart # ๐งช Checked-in ML-KEM KAT runner (3000 vectors, VM-only)
โโโ mldsa_kat_test.dart # ๐งช Discovered ML-DSA KAT runner (18 files, all flavours, VM-only)
โโโ dsa_zetas_test.dart # ๐งฎ FIPS 204 Appendix B zetas + negacyclic NTT property
โโโ dsa_rounding_test.dart # ๐ Power2Round/Decompose/MakeHint/UseHint boundaries
โโโ dsa_negative_test.dart # ๐ซ Malformed pk/sig/hint/context: verify returns false
โโโ dsa_api_test.dart # ๐ Context binding, hedged vs deterministic, domain separation
โโโ sha2_test.dart # #๏ธโฃ SHA-256/384/512 (FIPS 180-4) for HashML-DSA pre-hash
โโโ keccak_test.dart # ๐งฑ FIPS 202 (SHA3/SHAKE) known-answer tests
โโโ roundtrip_test.dart # ๐ End-to-end KEM round-trip (runs on VM + web)
โโโ kem_validation_test.dart # ๐ Public key, secret key, and ciphertext checks
โโโ keygen_derivation_test.dart # ๐ G(dโk) + matrix XOF-ordering unit tests
โโโ pack_test.dart # ๐ฆ Serialization round-trip bounds
โโโ poly_test.dart # ๐งฎ Modular reduction properties
โโโ cbd_test.dart # ๐ Statistical distribution checks
โโโ data/
โโโ MLKEM/ # ML-KEM KAT corpus (512/768/1024) + README
โ โโโ kat_MLKEM_*.rsp
โโโ MLDSA/ # ML-DSA KAT corpus + README
โโโ kat_MLDSA_{44,65,87}_{det,hedged}_{raw,pure,hashed}.rsp
tool/
โโโ openssl_interop/ # ๐ OpenSSL FFI interop harness (dev tool, separate package)
โโโ lib/openssl_ml_kem.dart # generalized EVP bindings (512/768/1024)
โโโ bin/openssl_pqcrypto_interop.dart
โโโ test/interop_test.dart # rigorous AโG interop suite
.github/
โโโ workflows/
โโโ ci.yml # analyze + format + unit/KAT suite + web (dart2js/dart2wasm)
โโโ interop.yml # OpenSSL โ pqcrypto ML-KEM-512/768/1024 interop
๐ป Usage #
Quick Start #
Serverpod Users: Check out the Full Stack Integration Guide for a complete backend + client implementation pattern. Agent Workflows: The project-level Universal Multi-Agent PQC Framework provides Codex, Claude Code, and Antigravity wrappers plus an LLM-readable manifest for evidence-scoped Serverpod/Flutter PQC planning.
import 'package:pqcrypto/pqcrypto.dart';
void main() {
// 1. Select the security level
// Options: PqcKem.kyber512, PqcKem.kyber768, PqcKem.kyber1024
final kem = PqcKem.kyber768;
// 2. Generate Keypair (Server Side)
// Returns Public Key (pk) and Secret Key (sk)
final (pk, sk) = kem.generateKeyPair();
print('Public Key size: ${pk.length} bytes');
print('Secret Key size: ${sk.length} bytes');
// 3. Encapsulate (Client Side)
// Uses the Public Key to generate a Shared Secret and Ciphertext
final (ct, ssAlice) = kem.encapsulate(pk);
print('Ciphertext size: ${ct.length} bytes');
// 4. Decapsulate (Server Side)
// Server recovers the same Shared Secret using Secret Key
final ssBob = kem.decapsulate(sk, ct);
// Check that secrets match
assert(_bytesEqual(ssAlice, ssBob));
print('Shared Secret derived successfully!');
}
bool _bytesEqual(List<int> a, List<int> b) {
if (a.length != b.length) return false;
for (var i = 0; i < a.length; i++) {
if (a[i] != b[i]) return false;
}
return true;
}
๐งช Verification & Testing #
The quality of this cryptographic library is verified through these repository-local layers:
1. ML-KEM Known Answer Tests (KAT) #
Validates against the .rsp files checked into test/data.
- Parser:
test/kat_evaluator_test.darthandles.rspfiles withz,d,msg,seed,pk,sk,ct,ss,ct_n, andss_n. - Coverage:
- โ ML-KEM-512: 1000/1000 vectors
- โ ML-KEM-768: 1000/1000 vectors
- โ ML-KEM-1024: 1000/1000 vectors
2. Unit & Property Tests #
- Serialization (
test/pack_test.dart): Round-trip validation for all compressed bit-depths using modular distance overq. - Reduction (
test/poly_test.dart): VerifiesbarrettReducereturns canonical residues in[0, q - 1], including negative and boundary cases. - Statistical (
test/cbd_test.dart): Verifies the output distribution of the CBD sampler matches theoretical binomial probabilities.
3. Validation & Negative Testing #
- Input validation (
test/kem_validation_test.dart): Confirms malformed public keys, malformed secret keys, and wrong ciphertext lengths are rejected. - Invalid decapsulation KATs (
test/kat_evaluator_test.dart): Confirms checked-inct_nvectors produce their expectedss_nshared secrets.
โก Performance #
Benchmarks on commodity Linux x64 hardware (Dart 3.x VM, JIT):
| Algorithm | Key Generation | Encapsulation | Decapsulation | Security Level |
|---|---|---|---|---|
| ML-KEM-512 | ~0.7 ms | ~0.7 ms | ~0.6 ms | 128-bit security |
| ML-KEM-768 | ~1.3 ms | ~1.4 ms | ~1.0 ms | 192-bit security |
| ML-KEM-1024 | ~1.8 ms | ~1.8 ms | ~1.7 ms | 256-bit security |
๐ฎ Roadmap #
- โ Phase 1: Foundation (Project structure, Poly math)
- โ Phase 2: Correctness (GenMatrix, CBD, FO Transform)
- โ Phase 3: FIPS 203 Alignment (NTT, Compression, ByteEncode)
- โ Phase 4: Full Suite (ML-KEM-512/768/1024 support)
- โ Phase 5: ML-DSA validation (byte-exact FIPS 204 KATs for 44/65/87 across raw/pure/hashed ร det/hedged; external hedged API; HashML-DSA; repo-local corpus)
- ๐ Phase 6: Hardening and expansion (best-effort zeroization and constant-time review landed; ongoing side-channel review, SLH-DSA/HQC research)
See doc/ROADMAP.md for the evidence-scoped roadmap.
Installation #
Add to pubspec.yaml:
dependencies:
pqcrypto: ^0.3.0
pqcrypto pulls in no third-party dependencies of its own.