Metadata-Version: 2.5
Name: sk-pqc
Version: 0.1.2
Summary: Sovereign, app-agnostic hybrid post-quantum crypto primitives: hybrid X25519+ML-KEM-768 KEM (FIPS 203), PQXDH-style seal, metadata-sealing routing envelope, epoch ratchets, crypto-agility registry. Python sibling of the Dart sk_pqc.
Project-URL: Homepage, https://github.com/smilinTux/sk-pqc-py
Project-URL: Dart sibling, https://github.com/smilinTux/sk-pqc-dart
Project-URL: Standards, https://github.com/smilinTux/sk-standards
Author: smilinTux / SKWorld
License: Apache-2.0
License-File: LICENSE
Keywords: FIPS-203,ML-KEM,crypto-agility,hybrid,post-quantum,ratchet,sovereign
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security :: Cryptography
Requires-Python: >=3.10
Requires-Dist: cryptography>=42.0
Provides-Extra: docs
Requires-Dist: pdoc>=15; extra == 'docs'
Provides-Extra: pq
Requires-Dist: liboqs-python>=0.12; extra == 'pq'
Provides-Extra: test
Requires-Dist: hypothesis>=6.0; extra == 'test'
Requires-Dist: liboqs-python>=0.12; extra == 'test'
Requires-Dist: pytest>=8.0; extra == 'test'
Description-Content-Type: text/markdown

# sk-pqc (Python)

[![PyPI](https://img.shields.io/pypi/v/sk-pqc.svg)](https://pypi.org/project/sk-pqc/)
[![Python](https://img.shields.io/pypi/pyversions/sk-pqc.svg)](https://pypi.org/project/sk-pqc/)
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
[![Suite](https://img.shields.io/badge/suite-x25519--mlkem768-1f6feb.svg)](https://github.com/smilinTux/sk-standards)

```bash
pip install sk-pqc          # core; hybrid KEM needs the pq extra
pip install "sk-pqc[pq]"    # + the ML-KEM-768 leg (liboqs)
```

> ⚠️ **Experimental · pre-1.0 · NOT independently security-audited.** This is a clean-room
> **reference implementation** — tested and cross-impl-parity-verified against our Rust
> (`sk-pqc`) and Dart (`sk_pqc`) builds, but it has had **no third-party security audit,
> fuzzing, or formal review**. Primitives bind vetted libraries (`liboqs`/ML-KEM,
> `cryptography`); the original code is the wiring. **Review it yourself before production
> use.** We apply our own honest-claims discipline to the library itself: don't trust it
> beyond the evidence.

**`sk-pqc` is a small, app-agnostic Python library of vetted *hybrid post-quantum* cryptographic primitives.**
**Use it to add hybrid X25519 + ML-KEM-768 (FIPS 203) confidentiality to any app — without dragging in a messaging framework.**

It is the Python sibling of the public Dart [`sk_pqc`](https://github.com/smilinTux/sk-pqc-dart) package and is **byte-for-byte interoperable** with it (shared cross-impl KAT vector). Import name `sk_pqc`; PyPI name `sk-pqc`.

- **Maturity tier:** T2 — Hybrid KEM (key exchange/wrap is `HKDF(X25519 ‖ ML-KEM-768)`; signatures stay classical/optional-hybrid). Per [sk-standards CRYPTOGRAPHY_STANDARD](https://github.com/smilinTux/sk-standards).
- **License:** Apache-2.0 · **Python:** ≥ 3.10 · **Version:** 0.1.0

> **Honest claim.** "Hybrid" means a derived secret is confidential if **EITHER** the classical X25519 leg **OR** the ML-KEM-768 leg holds — it survives a future cryptographically-relevant quantum computer breaking X25519, and it survives a classical break of ML-KEM. It is **not** "quantum-proof", "quantum-safe", or "unbreakable". AES-256-GCM (the bulk cipher) is symmetric / Grover-only and already quantum-acceptable. Citations: **FIPS 203** (ML-KEM), **FIPS 204** (ML-DSA, registry only), RFC 7748 (X25519), RFC 5869 (HKDF), SP 800-38D (AES-GCM).

---

## What's in the box

| Module | Primitive | What it gives you |
|---|---|---|
| `sk_pqc.pqkem` | **Hybrid KEM** `x25519-mlkem768` | `hybrid_keypair / hybrid_encap / hybrid_decap`. ML-KEM-768 leg = liboqs (`oqs`), X25519 leg + HKDF combiner = pyca `cryptography`. The combiner is the only original crypto: `HKDF-SHA256(X25519_ss ‖ MLKEM768_ss)` — **concat-then-KDF, never XOR, never pure-PQ**. |
| `sk_pqc.pqdm` | **PQXDH-style seal** | `seal / open_sealed` a body to a recipient's published hybrid prekey (`PrekeyBundle`), AES-256-GCM under a KEM-derived key, with a **downgrade-lock AAD** that makes silent classical downgrade detectable. |
| `sk_pqc.pqroute` | **Metadata-sealing routing envelope** `pqroute1` | `seal_routed / open_routed / read_route_header`. Splits a plaintext next-hop header (a relay reads it, but it is AEAD-bound / tamper-evident) from a hybrid-sealed inner (final destination + content) a relay cannot read. |
| `sk_pqc.group_ratchet` | **Group epoch ratchet** | Per-epoch secret distributed once via the hybrid KEM; per-message keys derived symmetrically + index-addressable (loss/reorder tolerant). Forward secrecy across epochs, post-compromise security from independent epoch secrets. |
| `sk_pqc.dm_ratchet` | **1:1 DM epoch ratchet** | The pairwise analogue of the group ratchet (distinct HKDF domain labels — a DM key can never collide with a group key). |
| `sk_pqc.anon_queue` | **Anon-queue addressing + deniable auth** | `new_queue_pair` (uncorrelated recipient/sender ids), the `aqid:` address codec, and a repudiable `HMAC-SHA256` authenticator. Addressing + deniable-auth **only** — not a transport. |
| `sk_pqc.crypto_suites` | **Crypto-agility registry** | Machine-readable suite-ids → primitives + quantum-resistance status + FIPS refs. The honest predicate `is_quantum_resistant(suite_id)` no caller should hand-roll. |

**Never silently downgrades.** If the liboqs backend is missing, hybrid operations raise `PqKemUnavailable` (a hard error). The pure-pyca pieces — combiner KAT, suite registry, anon-queue codec/MAC, key derivation — work with no PQ backend at all.

---

## Architecture

```mermaid
flowchart TD
    subgraph backends["Vetted backends — no hand-rolled math"]
        OQS["liboqs (oqs)<br/>ML-KEM-768 · FIPS 203"]
        PYCA["pyca/cryptography<br/>X25519 · HKDF-SHA256 · AES-256-GCM"]
    end

    OQS --> KEM
    PYCA --> KEM

    KEM["pqkem<br/>hybrid X25519 ‖ ML-KEM-768 KEM<br/>HKDF(X25519_ss ‖ MLKEM_ss)"]

    KEM --> DM["pqdm<br/>PQXDH-style seal<br/>(downgrade-lock AAD)"]
    KEM --> ROUTE["pqroute1<br/>metadata-sealing<br/>routing envelope"]
    KEM --> GR["group_ratchet<br/>per-epoch group keys"]
    KEM --> DMR["dm_ratchet<br/>per-epoch 1:1 DM keys"]

    REG["crypto_suites<br/>agility registry<br/>+ honest self-report"]
    AQ["anon_queue<br/>aqid: addressing<br/>+ deniable HMAC auth"]

    REG -. "describes / status" .-> KEM
    REG -. "describes / status" .-> DM

    KEM --> VEC{{"cross-impl KAT vector<br/>↔ Dart sk_pqc"}}

    classDef prim fill:#e6f0ff,stroke:#369;
    classDef be fill:#eee,stroke:#999;
    class KEM,DM,ROUTE,GR,DMR,REG,AQ prim;
    class OQS,PYCA be;
```

---

## Install

```bash
# Core (pure-pyca pieces work; hybrid KEM needs the pq extra)
pip install sk-pqc

# With the post-quantum (ML-KEM-768) leg via liboqs
pip install "sk-pqc[pq]"
```

> The ML-KEM leg uses [`liboqs-python`](https://github.com/open-quantum-safe/liboqs-python) (import name `oqs`), which binds the native liboqs. Point `oqs` at a prebuilt `liboqs.so` with `OQS_INSTALL_PATH` (or `SK_PQC_LIBOQS`) to avoid a source build — `sk_pqc.pqkem.ensure_liboqs_path()` applies this best-effort on import.

## Quickstart

```python
from sk_pqc import hybrid_keypair, hybrid_encap, hybrid_decap

kp = hybrid_keypair()                       # 1216 B pub, 2432 B priv
ct, ss_sender = hybrid_encap(kp.public_key) # 1120 B ciphertext + 32 B secret
ss_recipient  = hybrid_decap(ct, kp.private_key)
assert ss_sender == ss_recipient            # secure if EITHER leg holds
```

```python
from sk_pqc import PrekeyBundle, seal, open_sealed, SUITE_ID

bundle = PrekeyBundle(suite=SUITE_ID, hybrid_public_hex=kp.public_key.hex())
blob = seal(b"top secret", bundle, sender="alice", recipient="bob")
assert open_sealed(blob, kp.private_key, sender="alice", recipient="bob") == b"top secret"
```

**1:1 DM epoch ratchet** — distribute one epoch secret over the hybrid KEM, then key
many messages off it symmetrically (the ~1.1 KB ML-KEM ciphertext is paid once per
epoch, not per message):

```python
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from sk_pqc import DmRatchet, hybrid_keypair
from sk_pqc.dm_ratchet import new_epoch_secret, wrap_dm_epoch_secret, unwrap_dm_epoch_secret

bob = hybrid_keypair()
e0 = new_epoch_secret()
bob_e0 = unwrap_dm_epoch_secret(wrap_dm_epoch_secret(e0, bob.public_key), bob.private_key)

alice = DmRatchet(epoch=0, epoch_secret=e0)
bob_r = DmRatchet(epoch=0, epoch_secret=bob_e0)

idx, key = alice.next_outbound_key()           # carry idx on the wire
nonce = os.urandom(12)
ct = AESGCM(key).encrypt(nonce, b"hi bob", None)
assert AESGCM(bob_r.message_key(index=idx)).decrypt(nonce, ct, None) == b"hi bob"
```

### Runnable examples

The [`examples/`](examples/) directory has self-contained scripts (each is also a
smoke test — they `assert` their own correctness):

| Script | What it shows |
|---|---|
| [`examples/hybrid_kem_roundtrip.py`](examples/hybrid_kem_roundtrip.py) | Hybrid KEM encap/decap roundtrip + using the shared secret as an AES-256-GCM key. |
| [`examples/dm_ratchet_roundtrip.py`](examples/dm_ratchet_roundtrip.py) | Two-party (Alice↔Bob) DM-ratchet roundtrip: per-epoch KEM wrap, symmetric per-message keys, out-of-order delivery, post-compromise rekey. |
| [`examples/bench.py`](examples/bench.py) | `timeit` micro-benchmark of keygen/encap/decap (the table below). |

```bash
SK_PQC_LIBOQS=$HOME/.local/lib/liboqs.so LD_LIBRARY_PATH=$HOME/.local/lib \
  python examples/hybrid_kem_roundtrip.py
SK_PQC_LIBOQS=$HOME/.local/lib/liboqs.so LD_LIBRARY_PATH=$HOME/.local/lib \
  python examples/dm_ratchet_roundtrip.py
```

## Benchmarks

Whole-operation timings for the hybrid KEM (each op includes **both** the X25519
leg and the ML-KEM-768 leg plus the HKDF combiner — what a caller actually pays).
Measured with [`examples/bench.py`](examples/bench.py) (`timeit`, 500 iters × 5
batches, median per call):

```
sk_pqc hybrid KEM bench  (suite x25519-mlkem768)
  python   3.12.3 (x86_64)
  platform Linux-6.17.0-35-generic-x86_64-with-glibc2.39

op          median (us)      mean (us)      ops/sec
----------------------------------------------------
keygen            226            279          ~4,400
encap             360            512          ~2,800
decap             341            416          ~2,900
```

Sub-millisecond per operation on a commodity x86-64 CPU (no GPU); the ML-KEM-768
leg dominates over X25519. These are machine-specific — **reproduce on your own
hardware** with `python examples/bench.py [iters]`. Note the per-epoch DM-ratchet
design means this KEM cost is amortised across every message in an epoch, not paid
per message.

## Test

```bash
# From a checkout (run from HOME to avoid local-namespace collisions)
cd ~ && python -m pytest /path/to/sk-pqc-py/tests -q
```

The cross-implementation interop gate (`test_pqkem.py::test_cross_impl_vector_matches_sk_pqc`) decapsulates the shared Dart/Python KAT vector and asserts the recorded shared secret — this is what proves the two implementations agree byte-for-byte. PQ tests skip cleanly if liboqs is unavailable; the pure-pyca combiner KAT + registry tests always run.

## API docs

Full HTML API reference (every public symbol, generated from docstrings with
[`pdoc`](https://pdoc.dev)) lives in [`docs/api/`](docs/api/index.html) — open
`docs/api/index.html` in a browser. The pages carry the same experimental /
not-audited banner as this README.

Regenerate after changing any public API (pure-Python, no compile):

```bash
pip install pdoc
scripts/build-api-docs.sh   # or: make docs
```

> ⚠️ **`docs/api/` is generated HTML committed to the repo, and nothing regenerates or
> validates it on push.** It is a snapshot that **will drift silently** from the
> docstrings. If it disagrees with `src/`, `src/` wins. Regenerating it is a manual
> step, so treat the pages as a convenience, not as the contract.

See also the prose [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).

## Self-report (claim evidence)

```python
from sk_pqc import get_suite, is_quantum_resistant
s = get_suite("x25519-mlkem768")
print(s.status.value, s.fips_refs, is_quantum_resistant("x25519-mlkem768"))
# hybrid-pq ('FIPS 203', 'RFC 7748', 'RFC 5869') True
```

**This output is machine-checked.** `SOP.md`'s `docs-evidence` block executes that
exact call on every push and asserts each field, so the claim cannot drift away from
the code without the docs-check gate going red. The registry it reads
(`crypto_suites`) is pure stdlib, so the self-report answers even where liboqs is
absent and the hybrid operations would raise `PqKemUnavailable`.

## Provenance / clean-room

The `pqroute1` routing split and the `anon_queue` addressing are inspired by no-identity / mix-network messaging designs (clean-room, original implementation) — only the protocol idea; **no third-party code was copied or translated**. The wire formats, codecs, and MAC constructions are original and built solely on the vetted backends above.

## Related projects / See also

- ↔️ **Sibling (Dart):** [sk_pqc](https://github.com/smilinTux/sk-pqc-dart) ([pub.dev](https://pub.dev/packages/sk_pqc)) — the Dart hybrid-KEM companion this package interoperates with (shared KAT vector).
- ↔️ **Sibling (Rust):** [sk-pqc](https://github.com/smilinTux/sk-pqc-rs) ([crates.io](https://crates.io/crates/sk-pqc)) — the Rust implementation of the same suite + wire formats (full module set: kem/pqdm/pqroute/ratchets/anon_queue/suites).
- ⬇️ **Used by:** [skcomms](https://github.com/smilinTux/skcomms) — sovereign multi-transport comms (envelope payload + routing seal).
- ⬇️ **Used by:** [skchat](https://github.com/smilinTux/skchat) — AI-native encrypted chat (group + 1:1 DM ratchets).
- ↔️ **Sibling:** [sk_pgp](https://github.com/smilinTux/sk_pgp) — sovereign OpenPGP-PQC signing library (the signature counterpart).
- 📐 **Standards:** [sk-standards](https://github.com/smilinTux/sk-standards) — crypto, data-flow, version, and doc/SOP standards this repo conforms to.
