Last updated: 2026-06-16
This document records the repository-local interoperability tooling for
pqcrypto against native providers. It covers two unpublished FFI path packages:
tool/openssl_interop/for OpenSSLlibcrypto;tool/liboqs_interop/for liboqs.
The published pqcrypto package remains pure Dart and zero-dependency. All FFI,
native library discovery, provider build scripts, and native test dependencies
live under tool/ and are excluded from the publish surface.
This is functional interoperability and byte-contract evidence. It is not a CMVP/FIPS 140 validation claim, not a constant-time proof, and not a statement that the native providers or this package are interchangeable as validated cryptographic modules. See FIPS_140_BOUNDARY.md.
| Provider | Algorithms | What is checked |
|---|---|---|
| OpenSSL | ML-KEM-512/768/1024 | Full ML-KEM A-G matrix: sizes, self-consistency, bidirectional decapsulation, seeded public-key equivalence, public-key import/export round-trip, and implicit-rejection agreement. |
| OpenSSL | ML-DSA-44/65/87 | Seeded key pairs and hedged pure signatures are byte-exact; signatures verify through both providers. |
| OpenSSL | all 12 SLH-DSA sets | Seeded key pairs, internal signatures, and external context-string signatures are byte-exact; signatures verify through OpenSSL. |
| liboqs | ML-KEM-512/768/1024 | Deterministic key generation and deterministic encapsulation are byte-exact; random exchanges interoperate in both directions; implicit rejection agrees. |
| liboqs | ML-DSA-44/65/87 | Context-string signatures generated by either provider verify in the other provider; tampering is rejected. |
| liboqs | all 12 pure SLH-DSA sets | Context-string signatures generated by either provider verify in the other provider. |
Provider-neutral metadata for the full algorithm inventory is centralized in
tool/interop_common/: three ML-KEM sets, three
ML-DSA sets, and 12 SLH-DSA sets with their standardized byte sizes.
Runtime pqcrypto imports no FFI and has no third-party runtime dependencies.
The root pubspec.yaml lists only dev dependencies (lints, test), while the
interop packages are publish_to: none path packages with their own ffi and
test dependencies.
Do not document the native tooling as a runtime feature. Correct wording:
pqcryptoships pure Dart primitives. The repository also contains unpublished OpenSSL and liboqs FFI harnesses that cross-check the current checkout against independent native implementations.
The OpenSSL tool package uses Dart FFI against libcrypto:
cd tool/openssl_interop
dart pub get
bash tool/build_openssl.sh
LIBCRYPTO_PATH=.native/openssl-4.0.1/lib/libcrypto.so \
dart test --concurrency=1
dart run bin/openssl_pqcrypto_interop.dartThe build script pins OpenSSL 4.0.1 and verifies the official source archive
digest before compilation. You may also point LIBCRYPTO_PATH at another
compatible libcrypto that exposes the standardized ML-KEM, ML-DSA, and
SLH-DSA algorithms.
The human-readable binary
bin/openssl_pqcrypto_interop.dart
is ML-KEM-focused. The complete package:test suite is split by family:
test/mlkem_interop_test.dart;test/mldsa_interop_test.dart;test/slhdsa_interop_test.dart.
ML-KEM (FIPS 203) is specified down to public-key, ciphertext, seed, and shared secret byte contracts. The OpenSSL suite exercises every ML-KEM level:
| Test | What it proves |
|---|---|
| sizes | OpenSSL and pqcrypto output sizes match the FIPS 203 constants. |
| A | OpenSSL keygen/encaps/decaps is internally self-consistent. |
| B | pqcrypto keygen/encaps/decaps is internally self-consistent. |
| C | OpenSSL decapsulates a pqcrypto ciphertext to the same 32-byte secret. |
| D | pqcrypto decapsulates an OpenSSL ciphertext to the same 32-byte secret. |
| E | The same 64-byte (d ‖ z) seed derives byte-identical public keys. |
| E-exchange | The seed-derived key pair interoperates both directions. |
| F | A pqcrypto public key imported into OpenSSL and re-exported is byte-identical. |
| G | FIPS 203 implicit rejection J(z ‖ c) agrees on an invalid ciphertext. |
| negative | pqcrypto rejects a truncated OpenSSL public key. |
The standardized ML-KEM byte sizes are:
| Algorithm | Public key | Secret key | Ciphertext | Shared secret |
|---|---|---|---|---|
| ML-KEM-512 | 800 | 1632 | 768 | 32 |
| ML-KEM-768 | 1184 | 2400 | 1088 | 32 |
| ML-KEM-1024 | 1568 | 3168 | 1568 | 32 |
Only public keys, ciphertexts, and deterministic test seeds cross the provider boundary. Expanded private keys are not a deployment wire format.
test/mldsa_interop_test.dart checks ML-DSA-44, ML-DSA-65, and ML-DSA-87.
For each set it:
- derives
pqcryptoand OpenSSL key pairs from the same 32-byte seed; - asserts public and secret keys are byte-identical;
- signs the same message/context with the same 32-byte hedging randomness;
- asserts the signatures are byte-identical;
- verifies the
pqcryptosignature with OpenSSL and the OpenSSL signature withpqcrypto.
This is independent-provider evidence for the external pure-signature path with FIPS 204 context strings. The file-based FIPS 204 KAT corpus remains the canonical algorithm evidence for every raw/pure/hashed and deterministic/hedged variant.
test/slhdsa_interop_test.dart checks all 12 FIPS 205 parameter sets. For each
set it:
- derives
pqcryptoand OpenSSL key pairs from the same seed material (SK.seed || SK.prf || PK.seed); - asserts public and secret keys are byte-identical;
- compares deterministic internal signatures through the source-only ACVP helper;
- compares external context-string signatures through the public API; and
- verifies signatures with OpenSSL.
The public package root exports Algorithms 21-25 through SlhDsa; Algorithms
18-20 stay source-only for ACVP and interop fixtures.
The liboqs tool package uses Dart FFI against liboqs.so:
cd tool/liboqs_interop
dart pub get
bash tool/build_liboqs.sh
LIBOQS_PATH=.native/liboqs-0.15.0/lib/liboqs.so \
dart test --concurrency=1The build script pins liboqs 0.15.0 to commit
97f6b86b1b6d109cfd43cf276ae39c2e776aed80, builds only the 18 algorithms under
test (3 ML-KEM, 3 ML-DSA, 12 SLH-DSA), and disables OpenSSL acceleration so the
provider remains an independent native implementation.
The suite is split by family:
test/mlkem_interop_test.dart;test/mldsa_interop_test.dart;test/slhdsa_interop_test.dart;test/build_profile_test.dartfor build-profile sanity.
For each ML-KEM set, the liboqs suite checks:
- deterministic key generation from the same seed is byte-exact;
- deterministic encapsulation from the same seed is byte-exact;
- random
pqcrypto -> liboqsandliboqs -> pqcryptoexchanges derive the same shared secret; - implicit rejection agrees for a fixed invalid ciphertext under seed-derived keys.
For ML-DSA and SLH-DSA, liboqs supplies an independent verification provider:
pqcryptosignatures with context strings verify in liboqs;- liboqs signatures with context strings verify in
pqcrypto; - malformed or tampered signature paths remain rejected where covered.
The liboqs tests intentionally avoid overstating byte-exact signature equivalence where the provider API does not expose the same deterministic randomness contract as the OpenSSL test path.
.github/workflows/interop.yml runs two
independent jobs on push, pull request, and manual dispatch:
- OpenSSL ↔ pqcrypto ML-KEM, ML-DSA, SLH-DSA
- cache/build OpenSSL 4.0.1;
- verify the OpenSSL binary exposes ML-KEM, ML-DSA, and SLH-DSA;
- analyze
tool/interop_commonandtool/openssl_interop; - run the complete OpenSSL test suite with
LIBCRYPTO_PATH; - run the ML-KEM human-readable harness.
- liboqs ↔ pqcrypto ML-KEM, ML-DSA, SLH-DSA
- cache/build liboqs 0.15.0 at the pinned commit;
- verify generic KEM and signature APIs are exported;
- analyze
tool/interop_commonandtool/liboqs_interop; - run the complete liboqs test suite with
LIBOQS_PATH.
This workflow is intentionally separate from the main pure-Dart CI because it builds native dependencies and uses FFI.
- Hybrid TLS-adjacent ML-KEM component. The ML-KEM encodings and shared
secret match OpenSSL, which supports
X25519MLKEM768-style integration work in application stacks that supply TLS/classical KEX/HKDF/AEAD. - Dart client ↔ native service migration. A Dart/Flutter app can exchange ML-KEM material with OpenSSL or liboqs-backed services where the protocol carries standard public keys and ciphertexts.
- Signature migration and cross-checking. ML-DSA and SLH-DSA signatures can be checked against native provider behavior before a service cutover.
- Regression guard. The interop workflow catches byte-contract drift that a pure in-repo round trip might miss.
- The OpenSSL and liboqs packages are development/CI harnesses, not runtime dependencies and not part of the public package API.
- Native provider support changes over time. The committed build scripts pin the
versions used by CI; a system library may need
LIBCRYPTO_PATHorLIBOQS_PATH. - ML-KEM is not authenticated transport. Interop proves the KEM byte contract, not a complete secure protocol.
- Interop does not replace the checked-in NIST KAT corpora. KATs remain the primary standards-conformance evidence; native interop is complementary independent implementation evidence.
- None of this is a CMVP/FIPS 140 validation, hard constant-time proof, or hard memory-erasure guarantee.
- FIPS 203 (ML-KEM): https://csrc.nist.gov/pubs/fips/203/final
- FIPS 204 (ML-DSA): https://csrc.nist.gov/pubs/fips/204/final
- FIPS 205 (SLH-DSA): https://csrc.nist.gov/pubs/fips/205/final
- OpenSSL: https://www.openssl.org/
- liboqs: https://github.com/open-quantum-safe/liboqs
- This repository's evidence boundary: FIPS_140_BOUNDARY.md