Skip to content

Latest commit

 

History

History
238 lines (183 loc) · 11.5 KB

File metadata and controls

238 lines (183 loc) · 11.5 KB

Native Provider Interoperability

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:

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.

Coverage Matrix

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.

Package Boundary

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:

pqcrypto ships pure Dart primitives. The repository also contains unpublished OpenSSL and liboqs FFI harnesses that cross-check the current checkout against independent native implementations.

OpenSSL Harness

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.dart

The 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.

OpenSSL ML-KEM A-G

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.

OpenSSL ML-DSA

test/mldsa_interop_test.dart checks ML-DSA-44, ML-DSA-65, and ML-DSA-87. For each set it:

  • derives pqcrypto and 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 pqcrypto signature with OpenSSL and the OpenSSL signature with pqcrypto.

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.

OpenSSL SLH-DSA

test/slhdsa_interop_test.dart checks all 12 FIPS 205 parameter sets. For each set it:

  • derives pqcrypto and 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.

liboqs Harness

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=1

The 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.dart for build-profile sanity.

liboqs ML-KEM

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 -> liboqs and liboqs -> pqcrypto exchanges derive the same shared secret;
  • implicit rejection agrees for a fixed invalid ciphertext under seed-derived keys.

liboqs ML-DSA and SLH-DSA

For ML-DSA and SLH-DSA, liboqs supplies an independent verification provider:

  • pqcrypto signatures 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.

CI Workflow

.github/workflows/interop.yml runs two independent jobs on push, pull request, and manual dispatch:

  1. 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_common and tool/openssl_interop;
    • run the complete OpenSSL test suite with LIBCRYPTO_PATH;
    • run the ML-KEM human-readable harness.
  2. 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_common and tool/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.

Use Cases

  • 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.

Caveats

  • 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_PATH or LIBOQS_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.

References