quantum-shield 0.3.1

Hybrid post-quantum cryptography: X25519 + ML-KEM-1024 encryption and Ed25519 + ML-DSA-87 signatures
Documentation
# Security model

This is an honest statement of what quantum-shield v2 does and does not
protect against. If a claim isn't here, don't assume it.

## Audit status

**Neither this library nor the underlying `ml-kem`/`ml-dsa` crates have been
independently audited.** The library implements FIPS 203/204 algorithms; it
is not FIPS-validated. Do not deploy it where certification is required.

## What the hybrid construction buys

Both primitives protect every operation, AND-composed:

- **Confidentiality**: the AEAD key is `SHA3-256(ss_mlkem || ss_x25519 ||
  transcript)`. Recovering it requires both the X25519 shared secret
  (classical hardness: CDH on Curve25519) and the ML-KEM-1024 shared secret
  (post-quantum hardness: MLWE). A quantum attacker running Shor's algorithm
  gets `ss_x25519` but not `ss_mlkem`; a lattice cryptanalysis breakthrough
  gets `ss_mlkem` but not `ss_x25519`.
- **Authenticity**: verification requires a valid Ed25519 *and* a valid
  ML-DSA-87 signature over the same framed message. Forgery requires
  breaking both.

Consequences: "harvest now, decrypt later" traffic recorded today stays
confidential against a future quantum computer, as long as ML-KEM-1024
holds.

## Threats addressed

- **Ciphertext tampering / malleability**: every envelope byte is either
  AEAD-encrypted or bound as associated data; the KDF additionally binds the
  full KEM transcript. Component swapping between envelopes fails.
- **Downgrade**: version and suite bytes are authenticated; unknown values
  are rejected; v1 artifacts are rejected outright. There is no negotiation
  to attack.
- **Signature stripping**: both signature fields are fixed-size and
  mandatory; there is no wire encoding without the post-quantum component
  (the 0.1.x vulnerability).
- **Cross-protocol / cross-context reuse**: KEM and signature inputs are
  domain-separated by labels; signatures additionally separate application
  contexts via the length-framed `context` parameter.
- **Decryption oracles**: ML-KEM implicit rejection plus a single uniform
  `DecryptionFailed` error; error values carry no algorithm detail.
- **Key-material hygiene**: private keys exist as seeds, zeroized on drop
  (`zeroize`), never printed by `Debug`. Randomness comes from the OS CSPRNG
  (`getrandom`) only.

## Threats NOT addressed

- **Side channels beyond best effort**: the underlying crates are written to
  be constant-time (dalek, RustCrypto `ml-kem`/`ml-dsa`), but no formal
  side-channel evaluation has been done on this composition. No claims about
  fault injection, electromagnetic leakage, or speculative-execution
  attacks.
- **Sender authentication of envelopes**: `seal` is anonymous (like
  `crypto_box_seal`). An envelope proves nothing about who sent it — sign
  the message if you need that.
- **Replay**: a valid envelope or signature can be replayed; deduplication
  and freshness are application concerns (put a nonce/timestamp in the
  message or context).
- **Key distribution and trust**: binding a `PublicKeyBundle` to an identity
  (PKI, TOFU, out-of-band verification) is out of scope.
- **Forward secrecy for the recipient**: the recipient's KEM keys are
  static. Compromise of a recipient's secret bundle decrypts all past
  envelopes addressed to it. Rotate keys if you need bounded exposure.
- **Denial of service**: parsing enforces sizes, but callers must still
  bound how many envelopes/signatures they process. Note that opening a
  multi-recipient envelope trial-decrypts every wrap (no recipient identifier
  is on the wire, for privacy), so an attacker can force up to
  `MAX_RECIPIENTS` (1024) hybrid decapsulations per message — rate-limit
  untrusted multi-recipient input.
- **Multi-recipient equivocation** is *addressed*: `QSM2` binds `SHA3-256(CEK)`
  as a commitment and every recipient checks it, so a malicious sender cannot
  deliver different plaintexts to different recipients (AES-GCM alone is not
  key-committing).
- **Rotation rollback**: a `RotationAttestation` never expires. It binds a
  caller-supplied monotonic `epoch`, but the library is stateless — a verifier
  **must** track the highest epoch accepted per signer and reject non-advancing
  ones, or a captured superseded attestation can roll it back.
- **Stream truncation** is only caught if the reader calls
  `StreamOpener::finish()`; a reader that drops the opener without finishing
  will accept a truncated stream.

## Operational guidance

- Rotate keys on personnel or system compromise, and consider periodic
  rotation to bound the forward-secrecy exposure above.
- Treat `to_secret_bytes()` output like any other long-term private key:
  KMS/HSM-backed storage, at-rest encryption, least privilege.
- Report vulnerabilities per [SECURITY.md]../SECURITY.md.