⚛️ sk_pqc
SKWorld · Sovereign Post-Quantum · Apache-2.0

Sovereign post-quantum cryptography you can actually install

A hybrid X25519 + ML-KEM-768 key-encapsulation library with one wire format across Python, Dart, and Rust. Secure if either leg holds. Harvest-now-decrypt-later starts the day you do nothing.

The hat-trick — one library, three ecosystems

The same hybrid KEM, the same bytes on the wire, published to PyPI, pub.dev, and crates.io. Every package imports as sk_pqc.

Python
pip install sk-pqc
Dart / Flutter
dart pub add sk_pqc
Rust
cargo add sk-pqc

Registry names differ by convention (sk-pqc on PyPI/crates.io, sk_pqc on pub.dev) — but you always import sk_pqc.

One wire format — seal in Python, open in Rust

Three implementations, one byte-exact contract. The hybrid KEM is the same construction everywhere — HKDF-SHA256(X25519_ss ‖ ML-KEM-768_ss), concatenate-then-KDF, never XOR, never pure-PQ — and a shared cross-implementation vector anchored to the NIST ACVP FIPS 203 keyGen seed proves every backend recovers the identical secret.

🐍 Python 🎯 Dart 🦀 Rust
public key 1216 B  ·  ciphertext 1120 B  ·  shared secret 32 B
FIPS 203
ML-KEM-768
RFC 7748
X25519
RFC 5869
HKDF-SHA256
3
languages, 1 vector

A message encapsulated by the Python package opens in the Dart app and the Rust service, byte for byte — the same hybrid construction that ships in TLS 1.3's X25519MLKEM768 and Signal's PQXDH, so the security argument inherits theirs. The web Dart backend even negotiates this in the browser, where no WebCrypto PQC API exists.

The interop matrix — what speaks what, honestly

The KEM and the DM ratchet are wire-verified in all three languages against one shared cross-implementation vector. The higher-layer primitives (group ratchet, message seal, routing envelope, anonymous addressing) live in Python and Rust today and reach Dart over the single Rust core — see below. No row is aspirational: a ✓ means there is a passing parity test, not a plan.

Capability 🐍 Python 🎯 Dart 🦀 Rust Cross-impl proof
Hybrid KEMx25519-mlkem768 · 1216 / 1120 / 32 B FIPS 203 ACVP keyGen vector; ct sealed by any impl decaps on the others, both directions
DM epoch ratchetderive_dm_message_key · 50-msg / 7-day rekey bridge Same KAT (epoch/index → 32-byte key) matches byte-for-byte across all three
Group epoch ratchetper-member hybrid-wrapped epoch secret → core Python ↔ Rust parity vector (group_ratchet)
PQ message sealPQXDH-style wrap + downgrade-lock AAD → core Python ↔ Rust: canonical-JSON AAD, pqdm1: tokens
Routing envelopepqroute1 relay-readable header + sealed inner → core Python ↔ Rust: route-header AAD parity
Anon queue addressingaqid: codec + deniable HMAC tag → core Python ↔ Rust: address codec parity
Suite registry + self-reportcrypto-agility seam + honesty gate → core Rust pins the exact Python report byte-string as a vector
implemented & parity-tested bridge thin Dart port, validated against the same KAT, slated to fold into the Rust core → core reaches Dart via the single Rust core over FFI (in progress)

Cross-node, not just cross-language. The same bytes have been proven on the wire between live nodes: web (noble) ↔ native (liboqs) agree, the Rust PyO3 wheel ↔ pure-Python sk_pqc decapsulate each other's ciphertexts both ways, and skchat/skcomms negotiate a hybrid post-quantum DM in the browser — verified end-to-end over Chrome DevTools Protocol.

Benchmarks — sub-millisecond, no GPU

Whole-operation timings for the hybrid KEM — each call pays both the X25519 leg and the ML-KEM-768 leg plus the HKDF combiner, which is what a caller actually spends. Measured with examples/bench.py (timeit, 500 iters × 5 batches, median per call) on a commodity x86-64 desktop, no GPU.

Python 3.12 · Linux x86-64 · suite x25519-mlkem768 · median per call
opmedianmeanops/sec
keygen~226 µs279 µs~4,400
encap~360 µs512 µs~2,800
decap~341 µs416 µs~2,900

The ML-KEM-768 leg dominates over X25519, and the whole thing still clears in well under a millisecond. These are machine-specificreproduce on your own hardware:

# Python — pip install "sk-pqc[pq]"
python examples/bench.py            # 200 iters (default)
python examples/bench.py 1000       # custom iteration count

And it is paid once per epoch, not per message: the DM and group ratchets distribute one hybrid-KEM-wrapped epoch secret, then derive every per-message key symmetrically off it. The ~1.1 KB ciphertext and the microseconds above amortise across a whole conversation epoch.

One core, many bindings — write the crypto once

Three hand-written copies of a security-critical key schedule is three places to silently drift. The direction is a single audited Rust core — pure Rust (ml-kem + x25519-dalek, no OpenSSL, no liboqs) — surfaced to every other language over FFI, so there is one place the crypto actually lives.

🦀 sk-pqc-rs
pure-Rust core · 9 modules · 99 in-tree tests
🐍 Python — PyO3✓ proven · abi3 wheel, cross-parity-tested vs pure-Python sk_pqc (both directions)
🎯 Dart — flutter_rust_bridge⟳ in progress · today's Dart ratchet is a thin bridge, validated on the same KAT, slated to become a shim over the core

Because the core is pure Rust, the Python wheel links clean — no system OpenSSL, no liboqs to ship. The PyO3 feature is off by default, so cargo test stays pure-Rust and crates.io sees a dependency-clean crate; the Python services opt in to being backed by the same audited core the Rust clients use. Until the Dart FFI lands, the Dart package keeps a deliberately thin, clearly-marked ratchet port (and binds vetted @noble/post-quantum on web, liboqs on native for the ML-KEM leg) — pinned to the identical cross-language vectors so it cannot drift unnoticed.

1
audited-dependency core
PyO3
Python binding — proven
frb
Dart binding — in progress
0
hand-rolled primitives

What it is — and what it isn't

Honesty is a feature. Overclaiming in cryptography tells people they're safe where they aren't, so here is the unvarnished boundary.

What sk_pqc is

  • A hybrid KEM: X25519 + ML-KEM-768, combined with HKDF
  • Secure if either leg holds — a quantum break of X25519 still leaves ML-KEM; a lattice break still leaves X25519
  • FIPS 203 ML-KEM at the -768 tier (the NIST-recommended default)
  • Bound, never hand-rolled: liboqs, @noble/post-quantum, pyca cryptography do the lattice/curve math
  • A loud error, never a silent downgrade, when a PQ backend is missing

What it is not

  • Not "quantum-proof" / "unbreakable" / "quantum-safe" — the defensible word is post-quantum
  • Not audited — no third-party security review yet; research-grade
  • Not signatures — KEM only; authentication is future work
  • Not CNSA-2.0 — deliberately the -768 tier, not the level-5 ceiling
  • Not a fix for AES — symmetric crypto is Grover-only and already fine; we don't fear-monger it

Threat model — what -768 hybrid does & doesn't stop

The concrete adversary this is built against is harvest-now, decrypt-later (HNDL): someone who records your ciphertext today and waits for a cryptographically-relevant quantum computer to break the classical key exchange later. Here is exactly where the line sits.

What the -768 hybrid protects

  • HNDL on confidentiality — recorded traffic stays sealed: breaking X25519 later still leaves the ML-KEM-768 leg, so the derived secret holds
  • Classical break today — a lattice surprise against ML-KEM still leaves X25519; you are covered if either leg holds
  • Forward secrecy across epochs — the ratchet rekeys (50 msgs / 7 days); a compromised epoch secret doesn't unseal past or future epochs
  • Symmetric crypto is already fine — AES-256-GCM is Grover-only and quantum-acceptable; the hard problem here is key distribution, which is what we address

What it does not

  • No authentication — KEM only. Without PQ signatures (FIPS 204 ML-DSA, gated as future work) it doesn't prove who you're talking to; an active MITM is out of scope for the KEM alone
  • Not CNSA-2.0 — deliberately the -768 tier (the internet default), not the level-5 -1024 ceiling government suites mandate
  • A classical-only ratchet is still HNDL-exposed — wrapping a classical KEM in a ratchet doesn't fix harvest-now; the self-report says so regardless of ratchet level
  • Unaudited reference impl — no third-party audit, fuzzing, or formal review yet; the wiring is original even though every primitive is vetted
  • Not endpoint security — it protects keys in transit, not a compromised device, a screenshot, or a bad RNG you feed it

The honest framing of "hybrid": confidential as long as either the classical X25519 leg or the post-quantum ML-KEM-768 leg is unbroken — never both required, never "quantum-proof." The report module mechanically rejects the words "quantum-proof", "quantum-safe", and "unbreakable" from any externally-visible note, and never marks a classical suite quantum-resistant. ML-KEM-768 is standardized as FIPS 203; the companion signature standard is FIPS 204 (ML-DSA) — referenced, not yet implemented.

Use it — three lines, same shape

Generate a keypair, encapsulate to the public key, decapsulate with the secret key. The shared secret is identical across languages.

# Python — pip install sk-pqc
import sk_pqc
pk, sk = sk_pqc.generate_keypair()        # 1216-byte hybrid public key
ct, ss = sk_pqc.encapsulate(pk)           # 1120-byte ciphertext, 32-byte secret
ss2    = sk_pqc.decapsulate(sk, ct)        # ss == ss2
// Dart — dart pub add sk_pqc
import 'package:sk_pqc/sk_pqc.dart';
final kp = await SkPqc.generateKeypair();
final enc = await SkPqc.encapsulate(kp.publicKey);  // enc.ciphertext, enc.sharedSecret
// Rust — cargo add sk-pqc
use sk_pqc::{generate_keypair, encapsulate, decapsulate};
let (pk, sk) = generate_keypair();
let (ct, ss) = encapsulate(&pk);
let ss2 = decapsulate(&sk, &ct);          // ss == ss2

API names shown are illustrative of the shared shape; see each package's README for exact signatures. The wire bytes (1216 / 1120 / 32) are the contract that doesn't change.

Where it comes from

sk_pqc is the KEM extracted from a real sovereign messaging migration. The full story — group ratchets, at-rest wrapping, a post-quantum signing root, and a self-report engine that structurally refuses to overclaim — is written up in the research paper.